diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 78e2254c665..951678707aa 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -175,6 +175,7 @@ app/controllers/v1/post911_gi_bill_statuses_controller.rb @department-of-veteran
app/controllers/v2/higher_level_reviews_controller.rb @department-of-veterans-affairs/benefits-decision-reviews-be @department-of-veterans-affairs/backend-review-group
app/mailers/application_mailer.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/mailers/ch31_submissions_report_mailer.rb @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+app/mailers/create_excel_files_mailer.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/mailers/create_daily_spool_files_mailer.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/mailers/create_staging_spool_files_mailer.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/mailers/dependents_application_failure_mailer.rb @department-of-veterans-affairs/benefits-dependents-management @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -242,6 +243,7 @@ app/models/eligible_data_class.rb @department-of-veterans-affairs/vfs-vaos @depa
app/models/evss_claim_document.rb @department-of-veterans-affairs/benefits-management-tools-be @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/models/evss_claim.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/models/evss_claims_sync_status_tracker.rb @department-of-veterans-affairs/benefits-management-tools-be @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+app/models/excel_file_event.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/models/expiry_scanner.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/models/external_services_redis/status.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/models/feature_toggle_event.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -305,6 +307,7 @@ app/models/rate_limited_search.rb @department-of-veterans-affairs/va-api-enginee
app/models/saml_request_tracker.rb @department-of-veterans-affairs/octo-identity
app/models/saved_claim.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/models/saved_claim/education_benefits/va_10203.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+app/models/saved_claim/education_benefits/va_10282.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/models/saved_claim/disability_compensation.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/models/saved_claim/dependency_claim.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/models/session.rb @department-of-veterans-affairs/octo-identity
@@ -369,9 +372,9 @@ app/serializers/async_transaction @department-of-veterans-affairs/vfs-authentica
app/serializers/async_transaction/base_serializer.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/serializers/attachment_serializer.rb @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
app/serializers/backend_statuses_serializer.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+app/serializers/benefits_intake_submission_serializer.rb @department-of-veterans-affairs/backend-review-group
app/serializers/category_serializer.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/serializers/cemetery_serializer.rb @department-of-veterans-affairs/mbs-core-team @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
-app/serializers/central_mail_submission_serializer.rb @department-of-veterans-affairs/backend-review-group
app/serializers/communication_groups_serializer.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/serializers/contact_serializer.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/vfs-mhv-integration @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/serializers/decision_review_evidence_attachment_serializer.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -615,7 +618,6 @@ app/sidekiq/benefits_intake_remediation_status_job.rb @department-of-veterans-af
app/sidekiq/benefits_intake_status_job.rb @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/bgs @department-of-veterans-affairs/benefits-dependents-management @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/central_mail/submit_form4142_job.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
-app/sidekiq/central_mail/submit_central_form686c_job.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/benefits-dependents-management
app/sidekiq/copay_notifications @department-of-veterans-affairs/vsa-debt-resolution @department-of-veterans-affairs/backend-review-group
app/sidekiq/cypress_viewport_updater @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
app/sidekiq/decision_review @department-of-veterans-affairs/benefits-decision-reviews-be @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -671,7 +673,7 @@ spec/sidekiq/simple_forms_api/form_remediation/upload_retry_job_spec @department
app/sidekiq/terms_of_use @department-of-veterans-affairs/octo-identity
app/sidekiq/test_user_dashboard @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/qa-standards @department-of-veterans-affairs/backend-review-group
app/sidekiq/test_user_dashboard/daily_maintenance.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/qa-standards @department-of-veterans-affairs/backend-review-group
-app/sidekiq/transactional_email_analytics_job.rb @department-of-veterans-affairs/backend-review-group
+app/sidekiq/transactional_email_analytics_job.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/backend-review-group
app/sidekiq/va_notify_dd_email_job.rb @department-of-veterans-affairs/va-notify-write @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/va_notify_email_job.rb @department-of-veterans-affairs/va-notify-write @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
app/sidekiq/vbms @department-of-veterans-affairs/benefits-dependents-management @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -831,7 +833,6 @@ docs/setup/running_docker.md @department-of-veterans-affairs/backend-review-grou
docs/setup/running_natively.md @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
docs/setup/setup_with_binstubs.md @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
docs/setup/codespaces.md @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/cto-engineers
-docs/setup/va_forms.md @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
docs/setup/virtual_machine_access.md @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
.github @department-of-veterans-affairs/backend-review-group
lib @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1058,7 +1059,6 @@ modules/my_health/spec/request/v1/prescriptions_request_spec.rb @department-of-v
modules/representation_management @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/accredited-representation-management
modules/simple_forms_api @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
modules/test_user_dashboard @department-of-veterans-affairs/qa-standards @department-of-veterans-affairs/backend-review-group
-modules/va_forms @department-of-veterans-affairs/lighthouse-banana-peels @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
modules/va_notify @department-of-veterans-affairs/va-notify-write @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
modules/vaos @department-of-veterans-affairs/vfs-vaos @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
modules/vaos/app/services/vaos/v2 @department-of-veterans-affairs/vfs-vaos @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1201,6 +1201,7 @@ spec/factories/education_career_counseling_claim_no_vet_information.rb @departme
spec/factories/education_career_counseling_claim.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/education_stem_automated_decisions.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/eligible_data_classes.rb @department-of-veterans-affairs/vfs-vaos @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/factories/excel_file_events.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/extract_statuses.rb @department-of-veterans-affairs/vfs-vaos @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/evss_claims.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/evss_intent_to_files.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1273,6 +1274,7 @@ spec/factories/user_verifications.rb @department-of-veterans-affairs/octo-identi
spec/factories/va0993.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/va0994.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/va10203.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/factories/va10282.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/va1990e.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/va1990n.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/factories/va1990.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1355,6 +1357,7 @@ spec/sidekiq/copay_notifications @department-of-veterans-affairs/vsa-debt-resolu
spec/sidekiq/decision_review @department-of-veterans-affairs/benefits-decision-reviews-be @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/education_form @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/education_form/create_daily_spool_files.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/govcio-vfep-codereviewers
+spec/sidekiq/education_form/create_daily_excel_files_spec.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/delete_old_pii_logs_job_spec.rb @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
spec/sidekiq/evss/dependents_application_job_spec.rb @department-of-veterans-affairs/benefits-dependents-management @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/evss/disability_compensation_form @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1400,7 +1403,7 @@ spec/sidekiq/sign_in/delete_expired_sessions_job_spec.rb @department-of-veterans
spec/sidekiq/simple_forms_api @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/terms_of_use @department-of-veterans-affairs/octo-identity
spec/sidekiq/test_user_dashboard @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/qa-standards @department-of-veterans-affairs/backend-review-group
-spec/sidekiq/transactional_email_analytics_job_spec.rb @department-of-veterans-affairs/backend-review-group
+spec/sidekiq/transactional_email_analytics_job_spec.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/backend-review-group
spec/sidekiq/va_notify_dd_email_job_spec.rb @department-of-veterans-affairs/va-notify-write @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/va_notify_email_job_spec.rb @department-of-veterans-affairs/va-notify-write @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/sidekiq/vbms @department-of-veterans-affairs/benefits-dependents-management @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1554,6 +1557,7 @@ spec/lib/zero_silent_failures @department-of-veterans-affairs/va-api-engineers @
spec/mailers/ch31_submissions_report_mailer_spec.rb @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/mailers/create_daily_spool_files_mailer_spec.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/mailers/create_staging_spool_files_mailer_spec.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/mailers/create_excel_files_mailer_spec.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/mailers/dependents_application_failure_mailer_spec.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/mailers/direct_deposit_mailer_spec.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/mailers/previews/appeals_api_daily_error_report_mailer_preview.rb @department-of-veterans-affairs/lighthouse-banana-peels @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1592,6 +1596,7 @@ spec/models/education_benefits_claim_spec.rb @department-of-veterans-affairs/my-
spec/models/education_benefits_submission_spec.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/education_stem_automated_decision_spec.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/evss_claims_sync_status_tracker_spec.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/models/excel_file_event_spec.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/external_services_redis @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/folder_spec.rb @department-of-veterans-affairs/vfs-vaos @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/form1010cg @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1601,6 +1606,7 @@ spec/models/form526_submission_spec.rb @department-of-veterans-affairs/Disabilit
spec/models/form526_submission_remediation_spec.rb @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/form_attachment_spec.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/form_profile_spec.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/models/form_profile_v2_spec.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/form_submission_spec.rb @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/form_submission_attempt_spec.rb @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/models/gi_bill_feedback_spec.rb @department-of-veterans-affairs/my-education-benefits @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1754,6 +1760,7 @@ spec/serializers/appointment_serializer_spec.rb @department-of-veterans-affairs/
spec/serializers/async_transaction/base_serializer_spec.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/serializers/attachment_serializer_spec.rb @department-of-veterans-affairs/vfs-10-10 @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/va-api-engineers
spec/serializers/backend_statuses_serializer_spec.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/serializers/benefits_intake_submission_serializer_spec.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group @department-of-veterans-affairs/benefits-dependents-management
spec/serializers/category_serializer_spec.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/serializers/central_mail_submission_serializer_spec.rb @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/serializers/cemetery_serializer_spec.rb @department-of-veterans-affairs/mbs-core-team @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -1852,6 +1859,7 @@ spec/support/database_cleaner.rb @department-of-veterans-affairs/va-api-engineer
spec/support/default_configuration_helper.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/disability_compensation_form @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/error_details.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
+spec/support/excel_helpers.rb @department-of-veterans-affairs/govcio-vfep-codereviewers @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/factory_bot.rb @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/fake_api_key_for_lighthouse.txt @department-of-veterans-affairs/backend-review-group
spec/support/financial_status_report_helpers.rb @department-of-veterans-affairs/vsa-debt-resolution @department-of-veterans-affairs/backend-review-group
@@ -1934,7 +1942,6 @@ spec/support/schemas_camelized/all_triage_teams.json @department-of-veterans-aff
spec/support/schemas_camelized/user_loa1.json @department-of-veterans-affairs/octo-identity
spec/support/schemas_camelized/user_loa3.json @department-of-veterans-affairs/octo-identity
spec/support/schemas_camelized/user_preferences.json @department-of-veterans-affairs/octo-identity
-spec/support/schemas_camelized/va_forms @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/schemas_camelized/vaos @department-of-veterans-affairs/vfs-vaos @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/schemas_camelized/va_profile @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/schemas_camelized/veteran_verification @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -2013,7 +2020,6 @@ spec/support/schemas/all_triage_teams.json @department-of-veterans-affairs/vfs-m
spec/support/schemas/upload_supporting_evidence.json @department-of-veterans-affairs/Disability-Experience @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/schemas/user_loa1.json @department-of-veterans-affairs/octo-identity
spec/support/schemas/user_loa3.json @department-of-veterans-affairs/octo-identity
-spec/support/schemas/va_forms @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/schemas/vaos @department-of-veterans-affairs/vfs-vaos @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/schemas/va_profile @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/schemas/veteran_verification @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
@@ -2130,7 +2136,6 @@ spec/support/vcr_cassettes/token_validation @department-of-veterans-affairs/ligh
spec/support/vcr_cassettes/travel_pay @department-of-veterans-affairs/travel-pay-integration @department-of-veterans-affairs/backend-review-group
spec/support/vcr_cassettes/uploads/validate_document.yml @department-of-veterans-affairs/pension-and-burials @department-of-veterans-affairs/backend-review-group
spec/spupport/vcr_cassettes/user/get_facilities_empty.yml @department-of-veterans-affairs/vfs-facilities-frontend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
-spec/support/vcr_cassettes/va_forms @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/vcr_cassettes/va_notify @department-of-veterans-affairs/va-notify-write @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/vcr_cassettes/vaos @department-of-veterans-affairs/vfs-vaos @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
spec/support/vcr_cassettes/va_profile @department-of-veterans-affairs/vfs-authenticated-experience-backend @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8d2e829b591..e99f778d717 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,6 +8,18 @@ on:
permissions:
contents: read
jobs:
+ compare_sha:
+ runs-on: ubuntu-latest
+ name: Compare sha
+ steps:
+ - name: Compare commit ids
+ run: |
+ echo "github.sha: ${{ github.sha }}"
+ echo "github.event.push.head_commit.id: ${{ github.event.push.head_commit.id }}"
+ echo "github.event.pull_request.merge_commit_sha: ${{ github.event.pull_request.merge_commit_sha }}"
+ echo "github.event.head_commit.id: ${{ github.event.head_commit.id }}"
+ echo "github.event.workflow_run.head_commit.id: ${{ github.event.workflow_run.head_commit.id }}"
+
build_and_push:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
name: Build and Push
@@ -39,7 +51,7 @@ jobs:
file: ./postman/Dockerfile
push: true
tags: |
- ${{ steps.login-ecr.outputs.registry }}/dsva/vets-api-postman:${{ github.sha }}
+ ${{ steps.login-ecr.outputs.registry }}/dsva/vets-api-postman:${{ github.event.workflow_run.head_commit.id }}
- name: Build vets-api Docker Image
uses: docker/build-push-action@v6
env:
@@ -52,7 +64,7 @@ jobs:
context: .
push: true
tags: |
- ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY }}:${{ github.sha }}
+ ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY }}:${{ github.event.workflow_run.head_commit.id }}
cache-from: type=registry,ref=$ECR_REGISTRY/$ECR_REPOSITORY
cache-to: type=inline
deploy:
@@ -63,7 +75,7 @@ jobs:
ecr_repository: "vets-api"
manifests_directory: "vets-api"
auto_deploy_envs: "dev staging prod sandbox"
- commit_sha: ${{ github.sha }}
+ commit_sha: ${{ github.event.workflow_run.head_commit.id }}
secrets:
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
diff --git a/.github/workflows/code_checks.yml b/.github/workflows/code_checks.yml
index 4eed468f194..f838f55de77 100644
--- a/.github/workflows/code_checks.yml
+++ b/.github/workflows/code_checks.yml
@@ -8,6 +8,17 @@ permissions:
contents: read
checks: write
jobs:
+ compare_sha:
+ runs-on: ubuntu-latest
+ name: Compare sha
+ steps:
+ - name: Compare commit ids
+ run: |
+ echo "github.sha: ${{ github.sha }}"
+ echo "github.event.push.head_commit.id: ${{ github.event.push.head_commit.id }}"
+ echo "github.event.pull_request.merge_commit_sha: ${{ github.event.pull_request.merge_commit_sha }}"
+ echo "github.event.head_commit.id: ${{ github.event.head_commit.id }}"
+
linting_and_security:
name: Linting and Security
env:
diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml
index 2a72505a5bd..e72d048b7fb 100644
--- a/.github/workflows/danger.yml
+++ b/.github/workflows/danger.yml
@@ -24,7 +24,7 @@ jobs:
bundler-cache: true
- name: Run Danger
- run: bundle exec danger --head=${{ github.sha }} --base=${{ github.event.pull_request.base.sha }} --verbose
+ run: bundle exec danger --verbose
- name: Add Danger Label
uses: actions-ecosystem/action-remove-labels@v1
diff --git a/.github/workflows/deploy-template.yml b/.github/workflows/deploy-template.yml
index 39e9b4cd52d..c5342eb9270 100644
--- a/.github/workflows/deploy-template.yml
+++ b/.github/workflows/deploy-template.yml
@@ -12,7 +12,7 @@ on:
auto_deploy_envs:
required: true
type: string
- commit_sha: # #${{ github.sha }}
+ commit_sha: # #${{ github.event.workflow_run.head_commit.id }}
required: true
type: string
secrets:
diff --git a/Dangerfile b/Dangerfile
index a8c8a497044..11a4fc1047e 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -3,8 +3,8 @@
require 'ostruct'
module VSPDanger
- HEAD_SHA = `git rev-parse --abbrev-ref HEAD`.chomp.freeze
- BASE_SHA = 'origin/master'
+ HEAD_SHA = ENV.fetch('GITHUB_HEAD_REF', '').empty? ? `git rev-parse --abbrev-ref HEAD`.chomp.freeze : "origin/#{ENV.fetch('GITHUB_HEAD_REF')}"
+ BASE_SHA = ENV.fetch('GITHUB_BASE_REF', '').empty? ? 'origin/master' : "origin/#{ENV.fetch('GITHUB_BASE_REF')}"
class Runner
def self.run
diff --git a/Gemfile b/Gemfile
index c55e94ca519..ffaf4b53e97 100644
--- a/Gemfile
+++ b/Gemfile
@@ -33,7 +33,6 @@ path 'modules' do
gem 'simple_forms_api'
gem 'test_user_dashboard'
gem 'travel_pay'
- gem 'va_forms'
gem 'va_notify'
gem 'vaos'
gem 'vba_documents'
@@ -64,9 +63,9 @@ gem 'combine_pdf'
gem 'config'
gem 'connect_vbms', git: 'https://github.com/adhocteam/connect_vbms', tag: 'v2.1.1', require: 'vbms'
gem 'csv'
+gem 'datadog'
gem 'date_validator'
-gem 'ddtrace'
-gem 'dogstatsd-ruby', '5.6.3'
+gem 'dogstatsd-ruby'
gem 'dry-struct'
gem 'dry-types'
gem 'ethon', '>=0.13.0'
@@ -217,7 +216,7 @@ group :development, :test do
gem 'guard-rspec'
gem 'parallel_tests'
gem 'pry-byebug'
- gem 'rack-test', '2.1.0', require: 'rack/test'
+ gem 'rack-test', '2.2.0', require: 'rack/test'
gem 'rack-vcr'
gem 'rainbow' # Used to colorize output for rake tasks
gem 'rspec-instrumentation-matcher'
diff --git a/Gemfile.lock b/Gemfile.lock
index a71db49aa3a..3bef26ca36f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -125,10 +125,6 @@ PATH
test_user_dashboard (0.1.0)
rails
travel_pay (0.1.0)
- va_forms (0.0.1)
- faraday
- nokogiri
- sidekiq
va_notify (0.1.0)
vaos (0.1.0)
sidekiq
@@ -154,7 +150,7 @@ GEM
GEM
remote: https://rubygems.org/
specs:
- Ascii85 (1.1.0)
+ Ascii85 (2.0.1)
aasm (5.5.0)
concurrent-ruby (~> 1.0)
actioncable (7.2.2.1)
@@ -210,7 +206,7 @@ GEM
activemodel (= 7.2.2.1)
activesupport (= 7.2.2.1)
timeout (>= 0.4.0)
- activerecord-import (1.8.1)
+ activerecord-import (2.0.0)
activerecord (>= 4.2)
activerecord-postgis-adapter (10.0.1)
activerecord (~> 7.2.0)
@@ -241,13 +237,14 @@ GEM
base64
gyoku (>= 0.4.0)
nokogiri
- argon2-kdf (0.2.0)
+ argon2-kdf (0.3.0)
+ fiddle
ast (2.4.2)
attr_extras (7.1.0)
awesome_print (1.9.2)
aws-eventstream (1.3.0)
- aws-partitions (1.1022.0)
- aws-sdk-core (3.214.0)
+ aws-partitions (1.1031.0)
+ aws-sdk-core (3.214.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -255,7 +252,7 @@ GEM
aws-sdk-kms (1.96.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sigv4 (~> 1.5)
- aws-sdk-s3 (1.176.1)
+ aws-sdk-s3 (1.177.0)
aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
@@ -272,8 +269,8 @@ GEM
bcp47 (0.3.3)
i18n
benchmark (0.4.0)
- bigdecimal (3.1.8)
- bigdecimal (3.1.8-java)
+ bigdecimal (3.1.9)
+ bigdecimal (3.1.9-java)
bindex (0.8.1)
blind_index (2.6.1)
activesupport (>= 7)
@@ -281,7 +278,7 @@ GEM
blueprinter (1.1.2)
bootsnap (1.18.4)
msgpack (~> 1.2)
- brakeman (6.2.2)
+ brakeman (7.0.0)
racc
breakers (0.7.1)
faraday (>= 1.2.0, < 3.0)
@@ -291,7 +288,7 @@ GEM
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
byebug (11.1.3)
- carrierwave (3.0.7)
+ carrierwave (3.1.0)
activemodel (>= 6.0.0)
activesupport (>= 6.0.0)
addressable (~> 2.6)
@@ -329,7 +326,7 @@ GEM
config (5.5.2)
deep_merge (~> 1.2, >= 1.2.1)
ostruct
- connection_pool (2.4.1)
+ connection_pool (2.5.0)
content_disposition (1.0.0)
cork (0.3.0)
colored2 (~> 3.1)
@@ -360,21 +357,18 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
- datadog-ci (0.8.3)
+ datadog (2.8.0)
+ datadog-ruby_core_source (~> 3.3)
+ libdatadog (~> 14.3.1.1.0)
+ libddwaf (~> 1.18.0.0.0)
msgpack
+ datadog-ruby_core_source (3.3.7)
date (3.4.1)
date (3.4.1-java)
date_time_precision (0.8.1)
date_validator (0.12.0)
activemodel (>= 3)
activesupport (>= 3)
- ddtrace (1.23.2)
- datadog-ci (~> 0.8.1)
- debase-ruby_core_source (= 3.3.1)
- libdatadog (~> 7.0.0.1.0)
- libddwaf (~> 1.14.0.0.0)
- msgpack
- debase-ruby_core_source (3.3.1)
debug (1.10.0)
irb (~> 1.10)
reline (>= 0.3.8)
@@ -384,7 +378,7 @@ GEM
thread_safe (~> 0.3, >= 0.3.1)
diff-lcs (1.5.1)
docile (1.4.0)
- dogstatsd-ruby (5.6.3)
+ dogstatsd-ruby (5.6.4)
domain_name (0.6.20240107)
down (5.4.2)
addressable (~> 2.8)
@@ -392,14 +386,16 @@ GEM
dry-configurable (1.1.0)
dry-core (~> 1.0, < 2)
zeitwerk (~> 2.6)
- dry-core (1.0.1)
+ dry-core (1.1.0)
concurrent-ruby (~> 1.0)
+ logger
zeitwerk (~> 2.6)
- dry-inflector (1.0.0)
+ dry-inflector (1.2.0)
dry-initializer (3.1.1)
- dry-logic (1.5.0)
+ dry-logic (1.6.0)
+ bigdecimal
concurrent-ruby (~> 1.0)
- dry-core (~> 1.0, < 2)
+ dry-core (~> 1.1)
zeitwerk (~> 2.6)
dry-schema (1.13.4)
concurrent-ruby (~> 1.0)
@@ -409,12 +405,12 @@ GEM
dry-logic (>= 1.4, < 2)
dry-types (>= 1.7, < 2)
zeitwerk (~> 2.6)
- dry-struct (1.6.0)
- dry-core (~> 1.0, < 2)
- dry-types (>= 1.7, < 2)
+ dry-struct (1.7.0)
+ dry-core (~> 1.1)
+ dry-types (~> 1.8)
ice_nine (~> 0.11)
zeitwerk (~> 2.6)
- dry-types (1.7.2)
+ dry-types (1.8.0)
bigdecimal (~> 3.0)
concurrent-ruby (~> 1.0)
dry-core (~> 1.0)
@@ -449,8 +445,8 @@ GEM
faraday (>= 0.8)
faraday-httpclient (2.0.1)
httpclient (>= 2.2)
- faraday-multipart (1.0.4)
- multipart-post (~> 2)
+ faraday-multipart (1.1.0)
+ multipart-post (~> 2.0)
faraday-net_http (3.1.1)
net-http
faraday-retry (2.2.1)
@@ -461,8 +457,8 @@ GEM
faraday_curl (0.0.2)
faraday (>= 0.9.0)
fastimage (2.3.1)
- ffi (1.17.0)
- ffi (1.17.0-java)
+ ffi (1.17.1)
+ ffi (1.17.1-java)
ffi-compiler (1.3.2)
ffi (>= 1.15.5)
rake
@@ -481,6 +477,7 @@ GEM
date_time_precision (>= 0.8)
mime-types (>= 3.0)
nokogiri (>= 1.11.4)
+ fiddle (1.1.6)
fitbit_api (1.0.0)
oauth2 (~> 2.0)
flipper (1.3.2)
@@ -535,15 +532,10 @@ GEM
google-cloud-env (2.2.1)
faraday (>= 1.0, < 3.a)
google-logging-utils (0.1.0)
- google-protobuf (4.28.3)
+ google-protobuf (4.29.2)
bigdecimal
rake (>= 13)
- google-protobuf (4.28.3-java)
- bigdecimal
- ffi (~> 1)
- ffi-compiler (~> 1)
- rake (>= 13)
- googleauth (1.12.0)
+ googleauth (1.12.2)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.2)
google-logging-utils (~> 0.1)
@@ -576,7 +568,7 @@ GEM
hashdiff (1.1.1)
hashery (2.1.2)
hashie (5.0.0)
- hexapdf (1.0.0)
+ hexapdf (1.0.3)
cmdparse (~> 3.0, >= 3.0.3)
geom2d (~> 0.4, >= 0.4.1)
openssl (>= 2.2.1)
@@ -599,7 +591,7 @@ GEM
i18n (1.14.6)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
- image_processing (1.12.2)
+ image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
io-console (0.8.0)
@@ -610,11 +602,12 @@ GEM
iso_country_codes (0.7.8)
jar-dependencies (0.5.1)
jmespath (1.6.2)
- jruby-openssl (0.15.1-java)
- json (2.9.0)
- json (2.9.0-java)
- json-schema (5.1.0)
+ jruby-openssl (0.15.2-java)
+ json (2.9.1)
+ json (2.9.1-java)
+ json-schema (5.1.1)
addressable (~> 2.8)
+ bigdecimal (~> 3.1)
json_schema (0.21.0)
json_schemer (2.3.0)
bigdecimal
@@ -625,7 +618,7 @@ GEM
jsonapi-serializer (2.2.0)
activesupport (>= 4.2)
jwe (0.4.0)
- jwt (2.9.3)
+ jwt (2.10.1)
base64
kms_encrypted (1.6.0)
activesupport (>= 6.1)
@@ -634,26 +627,28 @@ GEM
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
language_server-protocol (3.17.0.3)
- libdatadog (7.0.0.1.0)
- libdatadog (7.0.0.1.0-aarch64-linux)
- libdatadog (7.0.0.1.0-x86_64-linux)
- libddwaf (1.14.0.0.0)
+ libdatadog (14.3.1.1.0)
+ libdatadog (14.3.1.1.0-aarch64-linux)
+ libdatadog (14.3.1.1.0-x86_64-linux)
+ libddwaf (1.18.0.0.0)
ffi (~> 1.0)
- libddwaf (1.14.0.0.0-aarch64-linux)
+ libddwaf (1.18.0.0.0-aarch64-linux)
ffi (~> 1.0)
- libddwaf (1.14.0.0.0-java)
+ libddwaf (1.18.0.0.0-java)
ffi (~> 1.0)
- libddwaf (1.14.0.0.0-x86_64-linux)
+ libddwaf (1.18.0.0.0-x86_64-linux)
ffi (~> 1.0)
- liquid (5.5.1)
+ liquid (5.6.0)
+ bigdecimal
+ strscan
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
llhttp-ffi (0.5.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
- lockbox (2.0.0)
- logger (1.6.3)
+ lockbox (2.0.1)
+ logger (1.6.4)
loofah (2.23.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@@ -679,8 +674,8 @@ GEM
minitest (5.25.4)
mock_redis (0.49.0)
redis (~> 5)
- msgpack (1.7.2)
- msgpack (1.7.2-java)
+ msgpack (1.7.5)
+ msgpack (1.7.5-java)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.4.1)
@@ -705,9 +700,15 @@ GEM
nio4r (2.7.4-java)
nkf (0.2.0)
nkf (0.2.0-java)
- nokogiri (1.17.2)
+ nokogiri (1.18.1)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
+ nokogiri (1.18.1-aarch64-linux-gnu)
+ racc (~> 1.4)
+ nokogiri (1.18.1-java)
+ racc (~> 1.4)
+ nokogiri (1.18.1-x86_64-linux-gnu)
+ racc (~> 1.4)
nori (2.7.1)
bigdecimal
notiffany (0.1.3)
@@ -725,7 +726,7 @@ GEM
octokit (9.2.0)
faraday (>= 1, < 3)
sawyer (~> 0.9)
- oj (3.16.7)
+ oj (3.16.9)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
okcomputer (1.18.5)
@@ -734,14 +735,15 @@ GEM
rails (>= 4.0)
open4 (1.3.4)
openapi_parser (2.1.0)
- openssl (3.2.0)
- openssl (3.2.0-java)
+ openssl (3.3.0)
+ openssl (3.3.0-java)
jruby-openssl (~> 0.14)
operating_hours (0.1.0)
optimist (3.2.0)
os (1.1.4)
ostruct (0.6.1)
- ox (2.14.18)
+ ox (2.14.19)
+ bigdecimal (>= 3.0)
parallel (1.26.3)
parallel_tests (4.7.2)
parallel
@@ -757,8 +759,8 @@ GEM
safe_shell (>= 1.0.3, < 2.0)
pdf-inspector (1.3.0)
pdf-reader (>= 1.0, < 3.0.a)
- pdf-reader (2.12.0)
- Ascii85 (~> 1.0)
+ pdf-reader (2.13.0)
+ Ascii85 (>= 1.0, < 3.0, != 2.0.0)
afm (~> 0.2.1)
hashery (~> 2.0)
ruby-rc4
@@ -774,7 +776,7 @@ GEM
matrix (~> 0.4)
pdf-core (~> 0.10.0)
ttfunk (~> 1.8)
- prawn-markup (1.0.0)
+ prawn-markup (1.0.1)
nokogiri
prawn
prawn-table
@@ -817,7 +819,7 @@ GEM
rack (~> 2.2, >= 2.2.4)
rack-session (1.0.2)
rack (< 3)
- rack-test (2.1.0)
+ rack-test (2.2.0)
rack (>= 1.3)
rack-timeout (0.7.0)
rack-vcr (0.1.6)
@@ -874,7 +876,7 @@ GEM
connection_pool
redis-namespace (1.11.0)
redis (>= 4)
- regexp_parser (2.9.3)
+ regexp_parser (2.10.0)
reline (0.6.0)
io-console (~> 0.5)
representable (3.2.0)
@@ -884,8 +886,8 @@ GEM
request_store (1.7.0)
rack (>= 1.4)
require_all (3.0.0)
- restforce (7.5.0)
- faraday (>= 1.1.0, < 2.12.0)
+ restforce (8.0.0)
+ faraday (>= 1.1.0, < 3.0.0)
faraday-follow_redirects (<= 0.3.0, < 1.0.0)
faraday-multipart (>= 1.0.0, < 2.0.0)
faraday-net_http (< 4.0.0)
@@ -965,7 +967,7 @@ GEM
rubocop-factory_bot (2.26.1)
rubocop (~> 1.61)
rubocop-junit-formatter (0.1.4)
- rubocop-rails (2.27.0)
+ rubocop-rails (2.28.0)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.52.0, < 2.0)
@@ -982,8 +984,9 @@ GEM
ruby-saml (1.17.0)
nokogiri (>= 1.13.10)
rexml
- ruby-vips (2.2.1)
+ ruby-vips (2.2.2)
ffi (~> 1.12)
+ logger
rubyzip (2.3.2)
rufus-scheduler (3.9.2)
fugit (~> 1.1, >= 1.11.1)
@@ -1047,12 +1050,14 @@ GEM
socksify (1.7.1)
spoon (0.0.6)
ffi
- ssrf_filter (1.1.2)
+ ssrf_filter (1.2.0)
staccato (0.5.3)
- statsd-instrument (3.9.7)
+ statsd-instrument (3.9.8)
stringio (3.1.2)
- strong_migrations (2.0.2)
+ strong_migrations (2.1.0)
activerecord (>= 6.1)
+ strscan (3.1.2)
+ strscan (3.1.2-java)
super_diff (0.14.0)
attr_extras (>= 6.2.4)
diff-lcs
@@ -1065,7 +1070,7 @@ GEM
thread_safe (0.3.6-java)
tilt (2.3.0)
timecop (0.9.10)
- timeout (0.4.2)
+ timeout (0.4.3)
trailblazer-option (0.1.2)
ttfunk (1.8.0)
bigdecimal (~> 3.1)
@@ -1179,13 +1184,13 @@ DEPENDENCIES
csv
danger
database_cleaner
+ datadog
date_validator
- ddtrace
debts_api!
debug
decision_reviews!
dhp_connected_devices!
- dogstatsd-ruby (= 5.6.3)
+ dogstatsd-ruby
dry-struct
dry-types
ethon (>= 0.13.0)
@@ -1273,7 +1278,7 @@ DEPENDENCIES
rack
rack-attack
rack-cors
- rack-test (= 2.1.0)
+ rack-test (= 2.2.0)
rack-timeout
rack-vcr
rails (~> 7.2.2)
@@ -1331,7 +1336,6 @@ DEPENDENCIES
travel_pay!
tzinfo-data
utf8-cleaner
- va_forms!
va_notify!
vaos!
vba_documents!
diff --git a/README.md b/README.md
index ec3b03f5948..e59dd269e70 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# Vets API
-This project provides common APIs for applications that live on VA.gov (formerly vets.gov APIs).
+This project provides common APIs for applications that live on VA.gov (formerly vets.gov APIs).
[![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/github/department-of-veterans-affairs/vets-api)
diff --git a/Rakefile b/Rakefile
index 5d35ae64d88..c02fcd6f940 100644
--- a/Rakefile
+++ b/Rakefile
@@ -4,7 +4,7 @@
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require 'rake'
-require 'ddtrace'
+require 'datadog'
require_relative 'config/application'
Datadog.configure do |c|
diff --git a/app/controllers/claims_base_controller.rb b/app/controllers/claims_base_controller.rb
index 50396360f55..ae17b787856 100644
--- a/app/controllers/claims_base_controller.rb
+++ b/app/controllers/claims_base_controller.rb
@@ -41,7 +41,7 @@ def create
def show
submission = CentralMailSubmission.joins(:central_mail_claim).find_by(saved_claims: { guid: params[:id] })
- render json: CentralMailSubmissionSerializer.new(submission)
+ render json: BenefitsIntakeSubmissionSerializer.new(submission)
end
private
diff --git a/app/controllers/concerns/exception_handling.rb b/app/controllers/concerns/exception_handling.rb
index a970d57f6cf..de8abed35aa 100644
--- a/app/controllers/concerns/exception_handling.rb
+++ b/app/controllers/concerns/exception_handling.rb
@@ -3,7 +3,7 @@
require 'common/exceptions'
require 'common/client/errors'
require 'json_schema/json_api_missing_attribute'
-require 'ddtrace'
+require 'datadog'
module ExceptionHandling
extend ActiveSupport::Concern
diff --git a/app/controllers/v1/post911_gi_bill_statuses_controller.rb b/app/controllers/v1/post911_gi_bill_statuses_controller.rb
index 2e620542d41..1b8569b3f93 100644
--- a/app/controllers/v1/post911_gi_bill_statuses_controller.rb
+++ b/app/controllers/v1/post911_gi_bill_statuses_controller.rb
@@ -9,8 +9,7 @@ class Post911GIBillStatusesController < ApplicationController
include SentryLogging
service_tag 'gibill-statement'
- STATSD_GI_BILL_TOTAL_KEY = 'api.lighthouse.gi_bill_status.total'
- STATSD_GI_BILL_FAIL_KEY = 'api.lighthouse.gi_bill_status.fail'
+ STATSD_KEY_PREFIX = 'api.post911_gi_bill_status'
def show
response = service.get_gi_bill_status
@@ -20,7 +19,7 @@ def show
rescue => e
handle_error(e)
ensure
- StatsD.increment(STATSD_GI_BILL_TOTAL_KEY)
+ StatsD.increment("#{STATSD_KEY_PREFIX}.total")
end
private
@@ -28,7 +27,7 @@ def show
def handle_error(e)
status = e.errors.first[:status].to_i
log_vet_not_found(@current_user, Time.now.in_time_zone('Eastern Time (US & Canada)')) if status == 404
- StatsD.increment(STATSD_GI_BILL_FAIL_KEY, tags: ["error:#{status}"])
+ StatsD.increment("#{STATSD_KEY_PREFIX}.fail", tags: ["error:#{status}"])
render json: { errors: e.errors }, status: status || :internal_server_error
end
diff --git a/app/controllers/v1/sessions_controller.rb b/app/controllers/v1/sessions_controller.rb
index bdf9d5cfe6f..07b957e1a27 100644
--- a/app/controllers/v1/sessions_controller.rb
+++ b/app/controllers/v1/sessions_controller.rb
@@ -154,12 +154,12 @@ def user_login(saml_response)
user_session_form = UserSessionForm.new(saml_response)
raise_saml_error(user_session_form) unless user_session_form.valid?
mhv_unverified_validation(user_session_form.user)
- create_user_verification(user_session_form.user)
+ user_verification = create_user_verification(user_session_form.user_identity)
@current_user, @session_object = user_session_form.persist
set_cookies
after_login_actions
- if @current_user.needs_accepted_terms_of_use
+ if user_verification.user_account.needs_accepted_terms_of_use?
redirect_to url_service.terms_of_use_redirect_url
else
redirect_to url_service.login_redirect_url
@@ -167,14 +167,14 @@ def user_login(saml_response)
login_stats(:success)
end
- def create_user_verification(user)
- user_verifier_object = OpenStruct.new({ sign_in: user.identity.sign_in,
- mhv_correlation_id: user.mhv_correlation_id,
- idme_uuid: user.idme_uuid,
- edipi: user.identity.edipi,
- logingov_uuid: user.logingov_uuid,
- icn: user.icn })
- Login::UserVerifier.new(user_verifier_object).perform
+ def create_user_verification(user_identity)
+ Login::UserVerifier.new(login_type: user_identity.sign_in&.dig(:service_name),
+ auth_broker: user_identity.sign_in&.dig(:auth_broker),
+ mhv_uuid: user_identity.mhv_credential_uuid,
+ idme_uuid: user_identity.idme_uuid,
+ dslogon_uuid: user_identity.edipi,
+ logingov_uuid: user_identity.logingov_uuid,
+ icn: user_identity.icn).perform
end
def mhv_unverified_validation(user)
diff --git a/app/mailers/create_excel_files_mailer.rb b/app/mailers/create_excel_files_mailer.rb
new file mode 100644
index 00000000000..675a6f2d30d
--- /dev/null
+++ b/app/mailers/create_excel_files_mailer.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class CreateExcelFilesMailer < ApplicationMailer
+ def build(filename)
+ date = Time.zone.now.strftime('%m/%d/%Y')
+ file_contents = File.read("tmp/#{filename}")
+ headers['Content-Disposition'] = "attachment; filename=#{filename}"
+
+ # rubocop:disable Layout/LineLength
+ recipients = Settings.vsp_environment.eql?('production') ? Settings.edu.production_excel_contents.emails : Settings.edu.staging_excel_contents.emails
+ subject = Settings.vsp_environment.eql?('production') ? "22-10282 Form CSV file for #{date}" : "Staging CSV file for #{date}"
+ # rubocop:enable Layout/LineLength
+
+ mail(
+ to: recipients,
+ subject: subject,
+ content_type: 'text/csv',
+ body: file_contents
+ )
+ end
+end
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index 97943846c0c..952d01fa13c 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -24,7 +24,7 @@ def self.descendants_using_encryption
def timestamp_attributes_for_update_in_model
kms_key_changed = changed? && changed.include?('encrypted_kms_key')
- called_from_kms_encrypted = caller_locations(1, 1)[0].label == 'encrypt_kms_keys'
+ called_from_kms_encrypted = caller_locations(1, 1)[0].base_label == 'encrypt_kms_keys'
# If update is due to kms key, don't update updated_at
kms_key_changed || called_from_kms_encrypted ? [] : super
diff --git a/app/models/education_benefits_claim.rb b/app/models/education_benefits_claim.rb
index 2fd99f8ea2c..0c7f43a97b6 100644
--- a/app/models/education_benefits_claim.rb
+++ b/app/models/education_benefits_claim.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class EducationBenefitsClaim < ApplicationRecord
- FORM_TYPES = %w[1990 1995 1990e 5490 5495 1990n 0993 0994 10203 1990s].freeze
+ FORM_TYPES = %w[1990 1995 1990e 5490 5495 1990n 0993 0994 10203 1990s 10282].freeze
APPLICATION_TYPES = %w[
chapter33
diff --git a/app/models/excel_file_event.rb b/app/models/excel_file_event.rb
new file mode 100644
index 00000000000..695a9d90f55
--- /dev/null
+++ b/app/models/excel_file_event.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class ExcelFileEvent < ApplicationRecord
+ validates :filename, uniqueness: true
+
+ # Look for an existing row with same filename
+ # and increase retry attempt if wasn't successful from previous attempt
+ # Otherwise create a new event
+ def self.build_event(filename)
+ filename_date = filename.match(/(.+)_/)[1]
+ event = find_by('filename like ?', "#{filename_date}%")
+
+ if event.present?
+ event.update(retry_attempt: event.retry_attempt + 1) if event.successful_at.nil?
+ return event
+ end
+
+ create(filename: filename)
+ end
+end
diff --git a/app/models/form_profile.rb b/app/models/form_profile.rb
index 3035ef5c188..d8de93d62f8 100644
--- a/app/models/form_profile.rb
+++ b/app/models/form_profile.rb
@@ -205,6 +205,7 @@ def prefill
@contact_information = initialize_contact_information
@military_information = initialize_military_information
form = form_id == '1010EZ' ? '1010ez' : form_id
+
if FormProfile.prefill_enabled_forms.include?(form)
mappings = self.class.mappings_for_form(form_id)
@@ -282,10 +283,14 @@ def vets360_contact_info_hash
def initialize_contact_information
opt = {}
opt.merge!(vets360_contact_info_hash) if vet360_contact_info
- Rails.logger.info("User Vet360 Contact Info, Address? #{opt[:address].present?}
- Email? #{opt[:email].present?}, Phone? #{opt[:home_phone].present?}")
-
+ if Flipper.enabled?(:remove_pciu, user)
+ # Monitor logs to validate the presence of Contact Information V2 user data
+ Rails.logger.info("VAProfile Contact Info: Address? #{opt[:address].present?},
+ Email? #{opt[:email].present?}, Phone? #{opt[:home_phone].present?}")
+ end
opt[:address] ||= user_address_hash
+
+ # The following pciu lines need to removed when tearing down the EVSS PCIU service.
opt[:email] ||= extract_pciu_data(:pciu_email)
if opt[:home_phone].nil?
opt[:home_phone] = pciu_primary_phone
@@ -293,7 +298,6 @@ def initialize_contact_information
end
format_for_schema_compatibility(opt)
-
FormContactInformation.new(opt)
end
@@ -302,7 +306,9 @@ def vet360_contact_info
return @vet360_contact_info if @vet360_contact_info_retrieved
@vet360_contact_info_retrieved = true
- if VAProfile::Configuration::SETTINGS.prefill && user.vet360_id.present?
+ if Flipper.enabled?(:remove_pciu, user) && user.icn.present?
+ @vet360_contact_info = VAProfileRedis::V2::ContactInformation.for_user(user)
+ elsif VAProfile::Configuration::SETTINGS.prefill && user.vet360_id.present?
@vet360_contact_info = VAProfileRedis::ContactInformation.for_user(user)
else
Rails.logger.info('Vet360 Contact Info Null')
@@ -330,7 +336,6 @@ def format_for_schema_compatibility(opt)
opt[:address][:street2] = apt[1]
opt[:address][:street] = opt[:address][:street].gsub(/\W?\s+#{apt[1]}/, '').strip
end
-
%i[home_phone us_phone mobile_phone].each do |phone|
opt[phone] = opt[phone].gsub(/\D/, '') if opt[phone]
end
@@ -362,6 +367,11 @@ def phone_object
home = vet360_contact_info&.home_phone
return home if home&.area_code && home.phone_number
+ if Flipper.enabled?(:remove_pciu, user)
+ # Track precense of home and mobile
+ Rails.logger.info("VAProfile Phone Object: Home? #{home.present?}, Mobile? #{mobile.present?}")
+ end
+
phone_struct = Struct.new(:area_code, :phone_number)
return phone_struct.new(pciu_us_phone.first(3), pciu_us_phone.last(7)) if pciu_us_phone&.length == 10
@@ -424,7 +434,7 @@ def clean!(value)
end
def clean_hash!(hash)
- hash.deep_transform_keys! { |k| k.camelize(:lower) }
+ hash.deep_transform_keys! { |k| k.to_s.camelize(:lower) }
hash.each { |k, v| hash[k] = clean!(v) }
hash.delete_if { |_k, v| v.blank? }
end
diff --git a/app/models/form_profiles/va_10282.rb b/app/models/form_profiles/va_10282.rb
new file mode 100644
index 00000000000..a8dd26e55cc
--- /dev/null
+++ b/app/models/form_profiles/va_10282.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class FormProfiles::VA10282 < FormProfile
+ def metadata
+ {
+ version: 0,
+ prefill: true,
+ returnUrl: '/applicant/information'
+ }
+ end
+end
diff --git a/app/models/form_profiles/va_686c674.rb b/app/models/form_profiles/va_686c674.rb
index 1f9911afadf..644f15af801 100644
--- a/app/models/form_profiles/va_686c674.rb
+++ b/app/models/form_profiles/va_686c674.rb
@@ -34,7 +34,13 @@ def metadata
private
def prefill_form_address
- mailing_address = VAProfileRedis::ContactInformation.for_user(user).mailing_address if user.vet360_id.present?
+ replace_pciu = Flipper.enabled?(:remove_pciu, user)
+ redis_prefill = if replace_pciu
+ VAProfileRedis::V2::ContactInformation.for_user(user)
+ else
+ VAProfileRedis::ContactInformation.for_user(user)
+ end
+ mailing_address = redis_prefill&.mailing_address if replace_pciu || user.vet360_id.present?
return if mailing_address.blank?
@form_address = FormAddress.new(
diff --git a/app/models/health_care_application.rb b/app/models/health_care_application.rb
index 090b2ea5f55..6618c3df800 100644
--- a/app/models/health_care_application.rb
+++ b/app/models/health_care_application.rb
@@ -6,6 +6,7 @@
require 'hca/enrollment_eligibility/service'
require 'hca/enrollment_eligibility/status_matcher'
require 'mpi/service'
+require 'hca/overrides_parser'
class HealthCareApplication < ApplicationRecord
include SentryLogging
@@ -71,7 +72,7 @@ def send_failure_email?
end
def submit_sync
- @parsed_form = override_parsed_form(parsed_form)
+ @parsed_form = HCA::OverridesParser.new(parsed_form).override
result = begin
HCA::Service.new(user).submit_form(parsed_form)
@@ -240,7 +241,7 @@ def prefill_fields
def submit_async
submission_job = email.present? ? 'SubmissionJob' : 'AnonSubmissionJob'
- @parsed_form = override_parsed_form(parsed_form)
+ @parsed_form = HCA::OverridesParser.new(parsed_form).override
"HCA::#{submission_job}".constantize.perform_async(
self.class.get_user_identifier(user),
@@ -315,8 +316,16 @@ def send_failure_email
def form_matches_schema
if form.present?
- JSON::Validator.fully_validate(VetsJsonSchema::SCHEMAS[self.class::FORM_ID], parsed_form).each do |v|
- errors.add(:form, v.to_s)
+ schema = VetsJsonSchema::SCHEMAS[self.class::FORM_ID]
+ begin
+ JSON::Validator.fully_validate(schema, parsed_form).each do |v|
+ errors.add(:form, v.to_s)
+ end
+ rescue => e
+ PersonalInformationLog.create(data: { schema:, parsed_form: },
+ error_class: 'HealthCareApplication FormValidationError')
+ Rails.logger.error("[#{FORM_ID}] Error during schema validation!", { error: e.message, schema: })
+ raise
end
end
end
diff --git a/app/models/persistent_attachments/va_form.rb b/app/models/persistent_attachments/va_form.rb
index ebd78adc69e..562273e73c7 100644
--- a/app/models/persistent_attachments/va_form.rb
+++ b/app/models/persistent_attachments/va_form.rb
@@ -9,7 +9,8 @@ class PersistentAttachments::VAForm < PersistentAttachment
{ max_pages: 10, min_pages: 1 }
).merge(
{
- '21-0779' => { max_pages: 4, min_pages: 2 }
+ '21-0779' => { max_pages: 4, min_pages: 2 },
+ '21-509' => { max_pages: 4, min_pages: 2 }
}
)
diff --git a/app/models/saved_claim/education_benefits/va_10282.rb b/app/models/saved_claim/education_benefits/va_10282.rb
new file mode 100644
index 00000000000..ac108796e24
--- /dev/null
+++ b/app/models/saved_claim/education_benefits/va_10282.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class SavedClaim::EducationBenefits::VA10282 < SavedClaim::EducationBenefits
+ add_form_and_validation('22-10282')
+
+ def after_submit(_user)
+ return unless Flipper.enabled?(:form22_10282_confirmation_email)
+
+ parsed_form_data = JSON.parse(form)
+ email = parsed_form_data['email']
+ return if email.blank?
+
+ send_confirmation_email(parsed_form_data, email)
+ end
+
+ private
+
+ def send_confirmation_email(parsed_form_data, email)
+ VANotify::EmailJob.perform_async(
+ email,
+ Settings.vanotify.services.va_gov.template_id.form22_10282_confirmation_email,
+ {
+ 'first_name' => parsed_form_data.dig('veteranFullName', 'first')&.upcase.presence,
+ 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'),
+ 'confirmation_number' => education_benefits_claim.confirmation_number
+ }
+ )
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index d7e99dc14d8..bdfc0a6e17f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -153,6 +153,7 @@ def mhv_account_type
end
def mhv_correlation_id
+ return unless can_create_mhv_account?
return mhv_user_account.id if mhv_user_account.present?
mpi_mhv_correlation_id if active_mhv_ids&.one?
@@ -315,6 +316,13 @@ def set_mhv_ids(mhv_id)
# Other MPI
+ def validate_mpi_profile
+ return unless mpi_profile?
+
+ raise MPI::Errors::AccountLockedError, 'Death Flag Detected' if mpi_profile.deceased_date
+ raise MPI::Errors::AccountLockedError, 'Theft Flag Detected' if mpi_profile.id_theft_flag
+ end
+
def invalidate_mpi_cache
return unless loa3? && mpi.mpi_response_is_cached? && mpi.mvi_response
@@ -404,7 +412,11 @@ def onboarding
def vet360_contact_info
return nil unless VAProfile::Configuration::SETTINGS.contact_information.enabled && vet360_id.present?
- @vet360_contact_info ||= VAProfileRedis::ContactInformation.for_user(self)
+ @vet360_contact_info ||= if Flipper.enabled?(:remove_pciu, self)
+ VAProfileRedis::V2::ContactInformation.for_user(self)
+ else
+ VAProfileRedis::ContactInformation.for_user(self)
+ end
end
def va_profile_email
@@ -469,12 +481,12 @@ def create_mhv_account_async
MHV::AccountCreatorJob.perform_async(user_verification_id)
end
+ private
+
def can_create_mhv_account?
loa3? && !needs_accepted_terms_of_use
end
- private
-
def mpi_profile
return nil unless identity && mpi
diff --git a/app/models/va_profile_redis/v2/cache.rb b/app/models/va_profile_redis/v2/cache.rb
index 48f8bcf0d75..e83ff1eb0fe 100644
--- a/app/models/va_profile_redis/v2/cache.rb
+++ b/app/models/va_profile_redis/v2/cache.rb
@@ -13,7 +13,7 @@ class Cache
# @param user [User] The current user
#
def self.invalidate(user)
- contact_info = VAProfileRedis::V2::ContactInformation.find(user.uuid)
+ contact_info = VAProfileRedis::V2::ContactInformation.find(user.icn)
contact_info.destroy if contact_info.present?
end
diff --git a/app/models/va_profile_redis/v2/contact_information.rb b/app/models/va_profile_redis/v2/contact_information.rb
index 4b25ec4d279..89ba80f8415 100644
--- a/app/models/va_profile_redis/v2/contact_information.rb
+++ b/app/models/va_profile_redis/v2/contact_information.rb
@@ -142,7 +142,7 @@ def response
end
# This method allows us to populate the local instance of a
- # VAProfileRedis::V2::ContactInformation object with the uuid necessary
+ # VAProfileRedis::V2::ContactInformation object with the icn necessary
# to perform subsequent actions on the key such as deletion.
def populate_from_redis
response_from_redis_or_service
@@ -167,11 +167,7 @@ def dig_out(key, type, matcher)
end
def response_from_redis_or_service
- unless VAProfile::Configuration::SETTINGS.contact_information.cache_enabled
- return contact_info_service.get_person
- end
-
- do_cached_with(key: @user.uuid) do
+ do_cached_with(key: @user.icn) do
contact_info_service.get_person
end
end
diff --git a/app/serializers/central_mail_submission_serializer.rb b/app/serializers/benefits_intake_submission_serializer.rb
similarity index 67%
rename from app/serializers/central_mail_submission_serializer.rb
rename to app/serializers/benefits_intake_submission_serializer.rb
index cb14b4321ea..100380bfe0d 100644
--- a/app/serializers/central_mail_submission_serializer.rb
+++ b/app/serializers/benefits_intake_submission_serializer.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CentralMailSubmissionSerializer
+class BenefitsIntakeSubmissionSerializer
include JSONAPI::Serializer
attribute :state
diff --git a/app/services/bgs/dependent_service.rb b/app/services/bgs/dependent_service.rb
index d19300ba717..260e491de68 100644
--- a/app/services/bgs/dependent_service.rb
+++ b/app/services/bgs/dependent_service.rb
@@ -99,9 +99,11 @@ def submit_to_central_service(claim:)
vet_info = JSON.parse(claim.form)['dependents_application']
user = BGS::SubmitForm686cJob.generate_user_struct(vet_info)
- CentralMail::SubmitCentralForm686cJob.perform_async(claim.id,
- KmsEncrypted::Box.new.encrypt(vet_info.to_json),
- KmsEncrypted::Box.new.encrypt(user.to_h.to_json))
+ Lighthouse::BenefitsIntake::SubmitCentralForm686cJob.perform_async(
+ claim.id,
+ KmsEncrypted::Box.new.encrypt(vet_info.to_json),
+ KmsEncrypted::Box.new.encrypt(user.to_h.to_json)
+ )
end
def external_key
diff --git a/app/services/login/user_verifier.rb b/app/services/login/user_verifier.rb
index a001bb30d1f..e537aeb04d0 100644
--- a/app/services/login/user_verifier.rb
+++ b/app/services/login/user_verifier.rb
@@ -2,14 +2,20 @@
module Login
class UserVerifier
- def initialize(user)
- @login_type = user.sign_in&.dig(:service_name)
- @auth_broker = user.sign_in&.dig(:auth_broker)
- @mhv_uuid = user.mhv_credential_uuid
- @idme_uuid = user.idme_uuid
- @dslogon_uuid = user.edipi
- @logingov_uuid = user.logingov_uuid
- @icn = user.icn.presence
+ def initialize(login_type:, # rubocop:disable Metrics/ParameterLists
+ auth_broker:,
+ mhv_uuid:,
+ idme_uuid:,
+ dslogon_uuid:,
+ logingov_uuid:,
+ icn:)
+ @login_type = login_type
+ @auth_broker = auth_broker
+ @mhv_uuid = mhv_uuid
+ @idme_uuid = idme_uuid
+ @dslogon_uuid = dslogon_uuid
+ @logingov_uuid = logingov_uuid
+ @icn = icn.presence
@deprecated_log = nil
@user_account_mismatch_log = nil
end
diff --git a/app/services/sign_in/user_code_map_creator.rb b/app/services/sign_in/user_code_map_creator.rb
index bc3de6fb069..c2f7aeccf1a 100644
--- a/app/services/sign_in/user_code_map_creator.rb
+++ b/app/services/sign_in/user_code_map_creator.rb
@@ -66,15 +66,6 @@ def device_sso
state_payload.scope == Constants::Auth::DEVICE_SSO
end
- def user_verifier_object
- @user_verifier_object ||= OpenStruct.new({ idme_uuid:,
- logingov_uuid:,
- sign_in:,
- edipi:,
- mhv_credential_uuid:,
- icn: verified_icn })
- end
-
def user_code_map
@user_code_map ||= UserCodeMap.new(login_code:,
type: state_payload.type,
@@ -84,7 +75,13 @@ def user_code_map
end
def user_verification
- @user_verification ||= Login::UserVerifier.new(user_verifier_object).perform
+ @user_verification ||= Login::UserVerifier.new(login_type: sign_in[:service_name],
+ auth_broker: sign_in[:auth_broker],
+ mhv_uuid: mhv_credential_uuid,
+ idme_uuid:,
+ dslogon_uuid: edipi,
+ logingov_uuid:,
+ icn: verified_icn).perform
end
def user_account
diff --git a/app/services/sign_in/user_loader.rb b/app/services/sign_in/user_loader.rb
index b4c43150346..f7a566ca424 100644
--- a/app/services/sign_in/user_loader.rb
+++ b/app/services/sign_in/user_loader.rb
@@ -31,6 +31,7 @@ def reload_user
current_user.session_handle = access_token.session_handle
current_user.save && user_identity.save
current_user.invalidate_mpi_cache
+ current_user.validate_mpi_profile
current_user.create_mhv_account_async
current_user
diff --git a/app/sidekiq/benefits_intake_status_job.rb b/app/sidekiq/benefits_intake_status_job.rb
index deccfc05ce7..f3324e33802 100644
--- a/app/sidekiq/benefits_intake_status_job.rb
+++ b/app/sidekiq/benefits_intake_status_job.rb
@@ -100,6 +100,7 @@ def handle_response(response)
# submission was successfully uploaded into a Veteran's eFolder within VBMS
form_submission_attempt.update(lighthouse_updated_at:)
form_submission_attempt.vbms!
+ monitor_success(form_id, saved_claim_id, uuid)
log_result('success', form_id, uuid, time_to_transition)
elsif time_to_transition > STALE_SLA.days
# exceeds SLA (service level agreement) days for submission completion
@@ -129,6 +130,25 @@ def log_result(result, form_id, uuid, time_to_transition = nil, error_message =
end
end
+ def monitor_success(form_id, saved_claim_id, bi_uuid)
+ # Remove this logic after SubmissionStatusJob replaces this one
+ if form_id == '21P-530EZ' && Flipper.enabled?(:burial_received_email_notification)
+ claim = SavedClaim::Burial.find_by(id: saved_claim_id)
+
+ unless claim
+ context = {
+ form_id: form_id,
+ claim_id: saved_claim_id,
+ benefits_intake_uuid: bi_uuid
+ }
+ Burials::Monitor.new.log_silent_failure(context, nil, call_location: caller_locations.first)
+ return
+ end
+
+ Burials::NotificationEmail.new(claim.id).deliver(:received)
+ end
+ end
+
# TODO: refactor - avoid require of module code, near duplication of process
# rubocop:disable Metrics/MethodLength
def monitor_failure(form_id, saved_claim_id, bi_uuid)
diff --git a/app/sidekiq/bgs/submit_form674_job.rb b/app/sidekiq/bgs/submit_form674_job.rb
index 5ee14c05022..a95ac35e5c9 100644
--- a/app/sidekiq/bgs/submit_form674_job.rb
+++ b/app/sidekiq/bgs/submit_form674_job.rb
@@ -85,13 +85,22 @@ def self.generate_user_struct(encrypted_user_struct, vet_info)
def self.send_backup_submission(encrypted_user_struct_hash, vet_info, saved_claim_id, user_uuid)
user = generate_user_struct(encrypted_user_struct_hash, vet_info)
- CentralMail::SubmitCentralForm686cJob.perform_async(saved_claim_id,
- KmsEncrypted::Box.new.encrypt(vet_info.to_json),
- KmsEncrypted::Box.new.encrypt(user.to_h.to_json))
+ Lighthouse::BenefitsIntake::SubmitCentralForm686cJob.perform_async(
+ saved_claim_id,
+ KmsEncrypted::Box.new.encrypt(vet_info.to_json),
+ KmsEncrypted::Box.new.encrypt(user.to_h.to_json)
+ )
InProgressForm.destroy_by(form_id: FORM_ID, user_uuid:)
rescue => e
- Rails.logger.warn('BGS::SubmitForm674Job backup submission failed...',
- { user_uuid:, saved_claim_id:, error: e.message, nested_error: e.cause&.message })
+ Rails.logger.warn(
+ 'BGS::SubmitForm674Job backup submission failed...',
+ {
+ user_uuid: user_uuid,
+ saved_claim_id: saved_claim_id,
+ error: e.message,
+ nested_error: e.cause&.message
+ }
+ )
InProgressForm.find_by(form_id: FORM_ID, user_uuid:)&.submission_pending!
end
diff --git a/app/sidekiq/bgs/submit_form686c_job.rb b/app/sidekiq/bgs/submit_form686c_job.rb
index 110c41b9f18..21f9c39680b 100644
--- a/app/sidekiq/bgs/submit_form686c_job.rb
+++ b/app/sidekiq/bgs/submit_form686c_job.rb
@@ -81,9 +81,11 @@ def self.generate_user_struct(vet_info)
def self.send_backup_submission(vet_info, saved_claim_id, user_uuid)
user = generate_user_struct(vet_info)
- CentralMail::SubmitCentralForm686cJob.perform_async(saved_claim_id,
- KmsEncrypted::Box.new.encrypt(vet_info.to_json),
- KmsEncrypted::Box.new.encrypt(user.to_h.to_json))
+ Lighthouse::BenefitsIntake::SubmitCentralForm686cJob.perform_async(
+ saved_claim_id,
+ KmsEncrypted::Box.new.encrypt(vet_info.to_json),
+ KmsEncrypted::Box.new.encrypt(user.to_h.to_json)
+ )
InProgressForm.destroy_by(form_id: FORM_ID, user_uuid:)
rescue => e
Rails.logger.warn('BGS::SubmitForm686cJob backup submission failed...',
diff --git a/app/sidekiq/central_mail/submit_central_form686c_job.rb b/app/sidekiq/central_mail/submit_central_form686c_job.rb
deleted file mode 100644
index 0191ce7c52b..00000000000
--- a/app/sidekiq/central_mail/submit_central_form686c_job.rb
+++ /dev/null
@@ -1,278 +0,0 @@
-# frozen_string_literal: true
-
-require 'central_mail/service'
-require 'benefits_intake_service/service'
-require 'pdf_utilities/datestamp_pdf'
-require 'pdf_info'
-require 'simple_forms_api_submission/metadata_validator'
-require 'dependents/monitor'
-
-module CentralMail
- class SubmitCentralForm686cJob
- include Sidekiq::Job
- include SentryLogging
-
- FOREIGN_POSTALCODE = '00000'
- FORM_ID = '686C-674'
- FORM_ID_674 = '21-674'
- STATSD_KEY_PREFIX = 'worker.submit_686c_674_backup_submission'
- # retry for 2d 1h 47m 12s
- # https://github.com/sidekiq/sidekiq/wiki/Error-Handling
- RETRY = 16
-
- attr_reader :claim, :form_path, :attachment_paths
-
- class CentralMailResponseError < StandardError; end
-
- def extract_uuid_from_central_mail_message(data)
- data.body[/(?<=\[).*?(?=\])/].split(': ').last if data.body.present?
- end
-
- sidekiq_options retry: RETRY
-
- sidekiq_retries_exhausted do |msg, _ex|
- if Flipper.enabled?(:dependents_trigger_action_needed_email)
- CentralMail::SubmitCentralForm686cJob.trigger_failure_events(msg)
- end
- end
-
- def perform(saved_claim_id, encrypted_vet_info, encrypted_user_struct)
- vet_info = JSON.parse(KmsEncrypted::Box.new.decrypt(encrypted_vet_info))
- user_struct = JSON.parse(KmsEncrypted::Box.new.decrypt(encrypted_user_struct))
- # if the 686c-674 has failed we want to call this central mail job (credit to submit_saved_claim_job.rb)
- # have to re-find the claim and add the relevant veteran info
- Rails.logger.info('CentralMail::SubmitCentralForm686cJob running!',
- { user_uuid: user_struct['uuid'], saved_claim_id:, icn: user_struct['icn'] })
- @claim = SavedClaim::DependencyClaim.find(saved_claim_id)
- claim.add_veteran_info(vet_info)
-
- get_files_from_claim
- result = upload_to_lh
- check_success(result, saved_claim_id, user_struct)
- rescue => e
- # if we fail, update the associated central mail record to failed and send the user the failure email
- Rails.logger.warn('CentralMail::SubmitCentralForm686cJob failed!',
- { user_uuid: user_struct['uuid'], saved_claim_id:, icn: user_struct['icn'], error: e.message })
- update_submission('failed')
- raise
- ensure
- cleanup_file_paths
- end
-
- def upload_to_lh
- Rails.logger.info({ message: 'SubmitCentralForm686cJob Lighthouse Initiate Submission Attempt',
- claim_id: claim.id })
- lighthouse_service = BenefitsIntakeService::Service.new(with_upload_location: true)
- uuid = lighthouse_service.uuid
- Rails.logger.info({ message: 'SubmitCentralForm686cJob Lighthouse Submission Attempt', claim_id: claim.id,
- uuid: })
- response = lighthouse_service.upload_form(
- main_document: split_file_and_path(form_path),
- attachments: attachment_paths.map(&method(:split_file_and_path)),
- form_metadata: generate_metadata_lh
- )
- create_form_submission_attempt(uuid)
-
- Rails.logger.info({ message: 'SubmitCentralForm686cJob Lighthouse Submission Successful', claim_id: claim.id,
- uuid: })
- response
- end
-
- def create_form_submission_attempt(intake_uuid)
- FormSubmissionAttempt.transaction do
- form_submission = FormSubmission.create(
- form_type: claim.submittable_686? ? FORM_ID : FORM_ID_674,
- saved_claim: claim,
- user_account: UserAccount.find_by(icn: claim.parsed_form['veteran_information']['icn'])
- )
- FormSubmissionAttempt.create(form_submission:, benefits_intake_uuid: intake_uuid)
- end
- end
-
- def get_files_from_claim
- # process the main pdf record and the attachments as we would for a vbms submission
- form_674_path = process_pdf(claim.to_pdf(form_id: FORM_ID_674), claim.created_at, FORM_ID_674) if claim.submittable_674? # rubocop:disable Layout/LineLength
- form_686c_path = process_pdf(claim.to_pdf(form_id: FORM_ID), claim.created_at, FORM_ID) if claim.submittable_686?
- @form_path = form_686c_path || form_674_path
- @attachment_paths = claim.persistent_attachments.map { |pa| process_pdf(pa.to_pdf, claim.created_at) }
- # Treat 674 as first attachment
- attachment_paths.insert(0, form_674_path) if form_686c_path.present? && form_674_path.present?
- end
-
- def cleanup_file_paths
- Common::FileHelpers.delete_file_if_exists(form_path)
- attachment_paths.each { |p| Common::FileHelpers.delete_file_if_exists(p) }
- end
-
- def check_success(response, saved_claim_id, user_struct)
- if response.success?
- Rails.logger.info('CentralMail::SubmitCentralForm686cJob succeeded!',
- { user_uuid: user_struct['uuid'], saved_claim_id:, icn: user_struct['icn'] })
- update_submission('success')
- send_confirmation_email(OpenStruct.new(user_struct))
- else
- Rails.logger.info('CentralMail::SubmitCentralForm686cJob Unsuccessful',
- { response: response['message'].presence || response['errors'] })
- raise CentralMailResponseError
- end
- end
-
- def create_request_body
- body = {
- 'metadata' => generate_metadata.to_json
- }
-
- body['document'] = to_faraday_upload(form_path)
-
- i = 0
- attachment_paths.each do |file_path|
- body["attachment#{i += 1}"] = to_faraday_upload(file_path)
- end
-
- body
- end
-
- def update_submission(state)
- claim.central_mail_submission.update!(state:) if claim.respond_to?(:central_mail_submission)
- end
-
- def to_faraday_upload(file_path)
- Faraday::UploadIO.new(
- file_path,
- Mime[:pdf].to_s
- )
- end
-
- def process_pdf(pdf_path, timestamp = nil, form_id = nil)
- stamped_path1 = PDFUtilities::DatestampPdf.new(pdf_path).run(text: 'VA.GOV', x: 5, y: 5, timestamp:)
- stamped_path2 = PDFUtilities::DatestampPdf.new(stamped_path1).run(
- text: 'FDC Reviewed - va.gov Submission',
- x: 400,
- y: 770,
- text_only: true
- )
- if form_id.present?
- stamped_pdf_with_form(form_id, stamped_path2, timestamp)
- else
- stamped_path2
- end
- end
-
- def get_hash_and_pages(file_path)
- {
- hash: Digest::SHA256.file(file_path).hexdigest,
- pages: PdfInfo::Metadata.read(file_path).pages
- }
- end
-
- def generate_metadata
- form = claim.parsed_form['dependents_application']
- veteran_information = form['veteran_information'].presence || claim.parsed_form['veteran_information']
- form_pdf_metadata = get_hash_and_pages(form_path)
- address = form['veteran_contact_information']['veteran_address']
- is_usa = address['country_name'] == 'USA'
- metadata = {
- 'veteranFirstName' => veteran_information['full_name']['first'],
- 'veteranLastName' => veteran_information['full_name']['last'],
- 'fileNumber' => veteran_information['file_number'] || veteran_information['ssn'],
- 'receiveDt' => claim.created_at.in_time_zone('Central Time (US & Canada)').strftime('%Y-%m-%d %H:%M:%S'),
- 'uuid' => claim.guid,
- 'zipCode' => is_usa ? address['zip_code'] : FOREIGN_POSTALCODE,
- 'source' => 'va.gov',
- 'hashV' => form_pdf_metadata[:hash],
- 'numberAttachments' => attachment_paths.size,
- 'docType' => claim.form_id,
- 'numberPages' => form_pdf_metadata[:pages]
- }
-
- validated_metadata = SimpleFormsApiSubmission::MetadataValidator.validate(metadata, zip_code_is_us_based: is_usa)
-
- validated_metadata.merge(generate_attachment_metadata(attachment_paths))
- end
-
- def generate_metadata_lh
- form = claim.parsed_form['dependents_application']
- # sometimes veteran_information is not in dependents_application, but claim.add_veteran_info will make sure
- # it's in the outer layer of parsed_form
- veteran_information = form['veteran_information'].presence || claim.parsed_form['veteran_information']
- address = form['veteran_contact_information']['veteran_address']
- {
- veteran_first_name: veteran_information['full_name']['first'],
- veteran_last_name: veteran_information['full_name']['last'],
- file_number: veteran_information['file_number'] || veteran_information['ssn'],
- zip: address['country_name'] == 'USA' ? address['zip_code'] : FOREIGN_POSTALCODE,
- doc_type: claim.form_id,
- claim_date: claim.created_at,
- source: 'va.gov backup dependent claim submission',
- business_line: 'CMP'
- }
- end
-
- def generate_attachment_metadata(attachment_paths)
- attachment_metadata = {}
- i = 0
- attachment_paths.each do |file_path|
- i += 1
- attachment_pdf_metadata = get_hash_and_pages(file_path)
- attachment_metadata["ahash#{i}"] = attachment_pdf_metadata[:hash]
- attachment_metadata["numberPages#{i}"] = attachment_pdf_metadata[:pages]
- end
- attachment_metadata
- end
-
- def send_confirmation_email(user)
- return if user.va_profile_email.blank?
-
- VANotify::ConfirmationEmail.send(
- email_address: user.va_profile_email,
- template_id: Settings.vanotify.services.va_gov.template_id.form686c_confirmation_email,
- first_name: user&.first_name&.upcase,
- user_uuid_and_form_id: "#{user.uuid}_#{FORM_ID}"
- )
- end
-
- def self.trigger_failure_events(msg)
- monitor = Dependents::Monitor.new
- saved_claim_id, _, encrypted_user_struct = msg['args']
- user_struct = JSON.parse(KmsEncrypted::Box.new.decrypt(encrypted_user_struct)) if encrypted_user_struct.present?
- claim = SavedClaim::DependencyClaim.find(saved_claim_id)
- email = claim.parsed_form.dig('dependents_application', 'veteran_contact_information', 'email_address') ||
- user_struct.try(:va_profile_email)
- monitor.track_submission_exhaustion(msg, email)
- claim.send_failure_email(email)
- end
-
- private
-
- def stamped_pdf_with_form(form_id, path, timestamp)
- PDFUtilities::DatestampPdf.new(path).run(
- text: 'Application Submitted on va.gov',
- x: form_id == '686C-674' ? 400 : 300,
- y: form_id == '686C-674' ? 675 : 775,
- text_only: true, # passing as text only because we override how the date is stamped in this instance
- timestamp:,
- page_number: form_id == '686C-674' ? 6 : 0,
- template: "lib/pdf_fill/forms/pdfs/#{form_id}.pdf",
- multistamp: true
- )
- end
-
- def log_cmp_response(response)
- log_message_to_sentry("vre-central-mail-response: #{response}", :info, {}, { team: 'vfs-ebenefits' })
- end
-
- def valid_claim_data(saved_claim_id, vet_info)
- claim = SavedClaim::DependencyClaim.find(saved_claim_id)
-
- claim.add_veteran_info(vet_info)
-
- raise Invalid686cClaim unless claim.valid?(:run_686_form_jobs)
-
- claim.formatted_686_data(vet_info)
- end
-
- def split_file_and_path(path)
- { file: path, file_name: path.split('/').last }
- end
- end
-end
diff --git a/app/sidekiq/decision_review/delete_saved_claim_records_job.rb b/app/sidekiq/decision_review/delete_saved_claim_records_job.rb
index f03b3148784..38b0784387d 100644
--- a/app/sidekiq/decision_review/delete_saved_claim_records_job.rb
+++ b/app/sidekiq/decision_review/delete_saved_claim_records_job.rb
@@ -12,6 +12,8 @@ class DeleteSavedClaimRecordsJob
STATSD_KEY_PREFIX = 'worker.decision_review.delete_saved_claim_records'
def perform
+ ActiveSupport::Deprecation.new.warn("#{self.class.name} job is deprecated and will be replaced by DR engine job")
+
return unless enabled?
deleted_records = ::SavedClaim.where(delete_date: ..DateTime.now).destroy_all
diff --git a/app/sidekiq/decision_review/failure_notification_email_job.rb b/app/sidekiq/decision_review/failure_notification_email_job.rb
index 52101894362..1bf3f95a5f0 100644
--- a/app/sidekiq/decision_review/failure_notification_email_job.rb
+++ b/app/sidekiq/decision_review/failure_notification_email_job.rb
@@ -20,6 +20,8 @@ class FailureNotificationEmailJob
STATSD_KEY_PREFIX = 'worker.decision_review.failure_notification_email'
def perform
+ ActiveSupport::Deprecation.new.warn("#{self.class.name} job is deprecated and will be replaced by DR engine job")
+
return unless should_perform?
send_form_emails
@@ -146,10 +148,6 @@ def record_form_email_send_successful(submission, notification_id)
params = { submitted_appeal_uuid: submission.submitted_appeal_uuid, appeal_type:, notification_id: }
Rails.logger.info('DecisionReview::FailureNotificationEmailJob form email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.form.email_queued", tags: ["appeal_type:#{appeal_type}"])
-
- tags = ["service:#{DecisionReviewV1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: form submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
def record_form_email_send_failure(submission, e)
@@ -172,10 +170,6 @@ def record_secondary_form_email_send_successful(secondary_form, notification_id)
notification_id: }
Rails.logger.info('DecisionReview::FailureNotificationEmailJob secondary form email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.secondary_form.email_queued", tags: ["appeal_type:#{appeal_type}"])
-
- tags = ["service:#{DecisionReviewV1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: secondary form submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
def record_secondary_form_email_send_failure(secondary_form, e)
@@ -204,10 +198,6 @@ def record_evidence_email_send_successful(upload, notification_id)
}
Rails.logger.info('DecisionReview::FailureNotificationEmailJob evidence email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.evidence.email_queued", tags: ["appeal_type:#{appeal_type}"])
-
- tags = ["service:#{DecisionReviewV1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: evidence submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
def record_evidence_email_send_failure(upload, e)
diff --git a/app/sidekiq/decision_review/form4142_submit.rb b/app/sidekiq/decision_review/form4142_submit.rb
index a0404d07fdc..3a8bfb2834d 100644
--- a/app/sidekiq/decision_review/form4142_submit.rb
+++ b/app/sidekiq/decision_review/form4142_submit.rb
@@ -18,9 +18,6 @@ class Form4142Submit
appeal_submission_id, _encrypted_payload, submitted_appeal_uuid = msg['args']
job_id = msg['jid']
- tags = ['service:supplemental-claims', 'function: secondary form submission to Lighthouse']
- StatsD.increment('silent_failure', tags:)
-
::Rails.logger.error(
{
error_message:,
@@ -98,10 +95,6 @@ def self.record_email_send_successful(submission, notification_id)
notification_id: }
Rails.logger.info('DecisionReview::Form4142Submit retries exhausted email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.retries_exhausted.email_queued")
-
- tags = ["service:#{DecisionReviewV1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: secondary form submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
private_class_method :record_email_send_successful
@@ -112,6 +105,9 @@ def self.record_email_send_failure(submission, e)
message: e.message }
Rails.logger.error('DecisionReview::Form4142Submit retries exhausted email error', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.retries_exhausted.email_error", tags: ["appeal_type:#{appeal_type}"])
+
+ tags = ['service:supplemental-claims', 'function: secondary form submission to Lighthouse']
+ StatsD.increment('silent_failure', tags:)
end
private_class_method :record_email_send_failure
end
diff --git a/app/sidekiq/decision_review/hlr_status_updater_job.rb b/app/sidekiq/decision_review/hlr_status_updater_job.rb
index f568c8d4944..eaaf36a4c74 100644
--- a/app/sidekiq/decision_review/hlr_status_updater_job.rb
+++ b/app/sidekiq/decision_review/hlr_status_updater_job.rb
@@ -4,6 +4,11 @@
module DecisionReview
class HlrStatusUpdaterJob < SavedClaimStatusUpdaterJob
+ def perform
+ ActiveSupport::Deprecation.new.warn("#{self.class.name} job is deprecated and will be replaced by DR engine job")
+ super
+ end
+
private
def records_to_update
diff --git a/app/sidekiq/decision_review/nod_status_updater_job.rb b/app/sidekiq/decision_review/nod_status_updater_job.rb
index dce7e857bce..0d89b1ce999 100644
--- a/app/sidekiq/decision_review/nod_status_updater_job.rb
+++ b/app/sidekiq/decision_review/nod_status_updater_job.rb
@@ -4,6 +4,11 @@
module DecisionReview
class NodStatusUpdaterJob < SavedClaimStatusUpdaterJob
+ def perform
+ ActiveSupport::Deprecation.new.warn("#{self.class.name} job is deprecated and will be replaced by DR engine job")
+ super
+ end
+
private
def records_to_update
diff --git a/app/sidekiq/decision_review/sc_status_updater_job.rb b/app/sidekiq/decision_review/sc_status_updater_job.rb
index 0082bdb2768..472ea067666 100644
--- a/app/sidekiq/decision_review/sc_status_updater_job.rb
+++ b/app/sidekiq/decision_review/sc_status_updater_job.rb
@@ -4,6 +4,11 @@
module DecisionReview
class ScStatusUpdaterJob < SavedClaimStatusUpdaterJob
+ def perform
+ ActiveSupport::Deprecation.new.warn("#{self.class.name} job is deprecated and will be replaced by DR engine job")
+ super
+ end
+
private
def records_to_update
diff --git a/app/sidekiq/decision_review/submit_upload.rb b/app/sidekiq/decision_review/submit_upload.rb
index 4ed82a734c1..e3de31ce9bd 100644
--- a/app/sidekiq/decision_review/submit_upload.rb
+++ b/app/sidekiq/decision_review/submit_upload.rb
@@ -22,10 +22,6 @@ class SubmitUpload
upload = AppealSubmissionUpload.find(appeal_submission_upload_id)
submission = upload.appeal_submission
- service_name = DecisionReviewV1::APPEAL_TYPE_TO_SERVICE_MAP[submission.type_of_appeal]
- tags = ["service:#{service_name}", 'function: evidence submission to Lighthouse']
- StatsD.increment('silent_failure', tags:)
-
::Rails.logger.error({ error_message:, message:, appeal_submission_upload_id:, job_id: })
StatsD.increment("#{STATSD_KEY_PREFIX}.permanent_error")
@@ -189,10 +185,6 @@ def self.record_email_send_successful(upload, submission, notification_id)
notification_id: }
Rails.logger.info('DecisionReview::SubmitUpload retries exhausted email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.retries_exhausted.email_queued")
-
- tags = ["service:#{DecisionReviewV1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: evidence submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
private_class_method :record_email_send_successful
@@ -204,6 +196,10 @@ def self.record_email_send_failure(upload, submission, e)
message: e.message }
Rails.logger.error('DecisionReview::SubmitUpload retries exhausted email error', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.retries_exhausted.email_error", tags: ["appeal_type:#{appeal_type}"])
+
+ service_name = DecisionReviewV1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]
+ tags = ["service:#{service_name}", 'function: evidence submission to Lighthouse']
+ StatsD.increment('silent_failure', tags:)
end
private_class_method :record_email_send_failure
end
diff --git a/app/sidekiq/education_form/create_daily_excel_files.rb b/app/sidekiq/education_form/create_daily_excel_files.rb
new file mode 100644
index 00000000000..39e8d668da2
--- /dev/null
+++ b/app/sidekiq/education_form/create_daily_excel_files.rb
@@ -0,0 +1,194 @@
+# frozen_string_literal: true
+
+require 'sentry_logging'
+require 'sftp_writer/factory'
+
+module EducationForm
+ class DailyExcelFileError < StandardError
+ end
+
+ class CreateDailyExcelFiles
+ MAX_RETRIES = 5
+ STATSD_KEY = 'worker.education_benefits_claim'
+ STATSD_FAILURE_METRIC = "#{STATSD_KEY}.failed_excel_file".freeze
+ LIVE_FORM_TYPES = ['22-10282'].freeze
+ AUTOMATED_DECISIONS_STATES = [nil, 'denied', 'processed'].freeze
+ EXCEL_FIELDS = %w[
+ name
+ first_name
+ last_name
+ military_affiliation
+ phone_number
+ email_address
+ country
+ state
+ race_ethnicity
+ gender
+ education_level
+ employment_status
+ salary
+ technology_industry
+ ].freeze
+ HEADERS = ['Name', 'First Name', 'Last Name', 'Select Military Affiliation',
+ 'Phone Number', 'Email Address', 'Country', 'State', 'Race/Ethnicity',
+ 'Gender of Applicant', 'What is your highest level of education?',
+ 'Are you currently employed?', 'What is your current salary?',
+ 'Are you currently working in the technology industry? (If so, please select one)'].freeze
+ include Sidekiq::Job
+ include SentryLogging
+ sidekiq_options queue: 'default',
+ unique_for: 30.minutes,
+ retry: 5
+
+ # rubocop:disable Metrics/MethodLength
+ def perform
+ retry_count = 0
+ filename = "22-10282_#{Time.zone.now.strftime('%m%d%Y_%H%M%S')}.csv"
+ excel_file_event = ExcelFileEvent.build_event(filename)
+ begin
+ records = EducationBenefitsClaim
+ .unprocessed
+ .joins(:saved_claim)
+ .where(
+ saved_claims: {
+ form_id: LIVE_FORM_TYPES
+ }
+ )
+ return false if federal_holiday?
+
+ if records.count.zero?
+ log_info('No records to process.')
+ return true
+ elsif retry_count.zero?
+ log_info("Processing #{records.count} application(s)")
+ end
+
+ # Format the records and write to CSV file
+ formatted_records = format_records(records)
+ write_csv_file(formatted_records, filename)
+
+ email_excel_files(filename)
+
+ # Make records processed and add excel file event for rake job
+ records.each { |r| r.update(processed_at: Time.zone.now) }
+ excel_file_event.update(number_of_submissions: records.count, successful_at: Time.zone.now)
+ rescue => e
+ StatsD.increment("#{STATSD_FAILURE_METRIC}.general")
+ if retry_count < MAX_RETRIES
+ log_exception(DailyExcelFileError.new("Error creating excel files.\n\n#{e}
+ Retry count: #{retry_count}. Retrying..... "))
+ retry_count += 1
+ sleep(10 * retry_count) # exponential backoff for retries
+ retry
+ else
+ log_exception(DailyExcelFileError.new("Error creating excel files.
+ Job failed after #{MAX_RETRIES} retries \n\n#{e}"))
+ end
+ end
+ true
+ end
+
+ def write_csv_file(records, filename)
+ retry_count = 0
+
+ begin
+ # Generate CSV string content instead of writing to file
+ csv_contents = CSV.generate do |csv|
+ # Add headers
+ csv << HEADERS
+
+ # Add data rows
+ records.each_with_index do |record, index|
+ log_info("Processing record #{index + 1}: #{record.inspect}")
+
+ begin
+ row_data = EXCEL_FIELDS.map do |field|
+ value = record.public_send(field)
+ value.is_a?(Hash) ? value.to_s : value
+ end
+
+ csv << row_data
+ rescue => e
+ log_exception(DailyExcelFileError.new("Failed to add row #{index + 1}:\n"))
+ log_exception(DailyExcelFileError.new("#{e.message}\nRecord: #{record.inspect}"))
+ next
+ end
+ end
+ end
+
+ # Write to file for backup/audit purposes
+ File.write("tmp/#{filename}", csv_contents)
+ log_info('Successfully created CSV file')
+
+ # Return the CSV contents
+ csv_contents
+ rescue => e
+ StatsD.increment("#{STATSD_FAILURE_METRIC}.general")
+ log_exception(DailyExcelFileError.new('Error creating CSV files.'))
+
+ if retry_count < MAX_RETRIES
+ log_exception(DailyExcelFileError.new("Retry count: #{retry_count}. Retrying..... "))
+ retry_count += 1
+ sleep(5)
+ retry
+ else
+ log_exception(DailyExcelFileError.new("Job failed after #{MAX_RETRIES} retries \n\n#{e}"))
+ end
+ end
+ end
+ # rubocop:enable Metrics/MethodLength
+
+ def format_records(records)
+ records.map do |record|
+ format_application(record)
+ end.compact
+ end
+
+ def format_application(data)
+ form = EducationForm::Forms::Base.build(data)
+ track_form_type("22-#{data.form_type}")
+ form
+ rescue => e
+ inform_on_error(data, e)
+ nil
+ end
+
+ def inform_on_error(claim, error = nil)
+ StatsD.increment("#{STATSD_KEY}.failed_formatting.22-#{claim.form_type}")
+ exception = if error.present?
+ FormattingError.new("Could not format #{claim.confirmation_number}.\n\n#{error}")
+ else
+ FormattingError.new("Could not format #{claim.confirmation_number}")
+ end
+ log_exception(exception)
+ end
+
+ private
+
+ def federal_holiday?
+ holiday = Holidays.on(Time.zone.today, :us, :observed)
+ if holiday.empty?
+ false
+ else
+ log_info("Skipping on a Holiday: #{holiday.first[:name]}")
+ true
+ end
+ end
+
+ def track_form_type(type)
+ StatsD.gauge("#{STATSD_KEY}.transmissions.#{type}", 1)
+ end
+
+ def log_exception(exception)
+ log_exception_to_sentry(exception)
+ end
+
+ def log_info(message)
+ logger.info(message)
+ end
+
+ def email_excel_files(contents)
+ CreateExcelFilesMailer.build(contents).deliver_now
+ end
+ end
+end
diff --git a/app/sidekiq/education_form/create_daily_fiscal_year_to_date_report.rb b/app/sidekiq/education_form/create_daily_fiscal_year_to_date_report.rb
index 43ac4595c20..04b9b6cba74 100644
--- a/app/sidekiq/education_form/create_daily_fiscal_year_to_date_report.rb
+++ b/app/sidekiq/education_form/create_daily_fiscal_year_to_date_report.rb
@@ -13,7 +13,7 @@ class CreateDailyFiscalYearToDateReport
daily_processed: 0
}.freeze
- FORM_TYPES = EducationBenefitsClaim::FORM_TYPES
+ FORM_TYPES = EducationBenefitsClaim::FORM_TYPES.reject { |form_type| form_type == '10282' }
FORM_TYPE_HEADERS = EducationBenefitsClaim.form_headers(FORM_TYPES).map do |form_header|
[form_header, '', '']
diff --git a/app/sidekiq/education_form/create_daily_year_to_date_report.rb b/app/sidekiq/education_form/create_daily_year_to_date_report.rb
index 2be85c56dd9..9cb86ead24c 100644
--- a/app/sidekiq/education_form/create_daily_year_to_date_report.rb
+++ b/app/sidekiq/education_form/create_daily_year_to_date_report.rb
@@ -11,7 +11,7 @@ class CreateDailyYearToDateReport
daily_processed: 0
}.freeze
- FORM_TYPES = EducationBenefitsClaim::FORM_TYPES
+ FORM_TYPES = EducationBenefitsClaim::FORM_TYPES.reject { |form_type| form_type == '10282' }
FORM_TYPE_HEADERS = EducationBenefitsClaim.form_headers(FORM_TYPES).map do |form_header|
[form_header, '', '']
diff --git a/app/sidekiq/education_form/forms/va_10282.rb b/app/sidekiq/education_form/forms/va_10282.rb
new file mode 100644
index 00000000000..ff8be795ac6
--- /dev/null
+++ b/app/sidekiq/education_form/forms/va_10282.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+
+module EducationForm::Forms
+ class VA10282 < Base
+ SALARY_TYPES = {
+ moreThanSeventyFive: 'More than $75,000',
+ thirtyFiveToFifty: '$35,000 - $50,000',
+ fiftyToSeventyFive: '$50,000 - $75,000',
+ twentyToThirtyFive: '$20,000 - $35,000',
+ lessThanTwenty: 'Less than $20,000'
+ }.freeze
+
+ TECH_AREAS = {
+ CP: 'Computer Programming',
+ DP: 'Data Processing',
+ CS: 'Cyber Security',
+ IS: 'Information Security',
+ MA: 'Mobile Applications',
+ NA: 'Not Applicable'
+ }.freeze
+
+ GENDER_TYPES = {
+ 'M' => 'Male',
+ 'W' => 'Female',
+ 'TW' => 'Transgender Woman',
+ 'TM' => 'Transgender Man',
+ 'NB' => 'Non-Binary',
+ '0' => 'Other',
+ 'NA' => 'Prefer Not to Answer'
+ }.freeze
+
+ EDUCATION_LEVELS = {
+ 'HS' => 'High School',
+ 'AD' => 'Associate Degree',
+ 'BD' => "Bachelor's Degree",
+ 'MD' => "Master's Degree",
+ 'DD' => 'Doctorate Degree',
+ 'NA' => 'Prefer Not to Answer'
+ }.freeze
+
+ MILITARY_TYPES = {
+ 'veteran' => 'Veteran',
+ 'veteransSpouse' => "Veteran's Spouse",
+ 'veteransChild' => "Veteran's Child",
+ 'veteransCaregiver' => "Veteran's Caregiver",
+ 'activeduty' => 'Active Duty',
+ 'nationalGuard' => 'National Guard',
+ 'reservist' => 'Reservist',
+ 'individualReadyReserve' => 'Individual Ready Reserve'
+ }.freeze
+
+ ETHNICITY_TYPES = {
+ 'HL' => 'Hispanic or Latino',
+ 'NHL' => 'Not Hispanic or Latino',
+ 'NA' => 'Prefer Not to Answer'
+ }.freeze
+
+ # rubocop:disable Lint/MissingSuper
+ def initialize(education_benefits_claim)
+ @education_benefits_claim = education_benefits_claim
+ @applicant = education_benefits_claim.parsed_form
+ end
+ # rubocop:enable Lint/MissingSuper
+
+ def name
+ "#{first_name} #{last_name}"
+ end
+
+ def first_name
+ @applicant['veteranFullName']['first']
+ end
+
+ def last_name
+ @applicant['veteranFullName']['last']
+ end
+
+ def military_affiliation
+ MILITARY_TYPES[@applicant['veteranDesc']] || 'Not specified'
+ end
+
+ def phone_number
+ @applicant.dig('contactInfo', 'mobilePhone') ||
+ @applicant.dig('contactInfo', 'homePhone') ||
+ 'Not provided'
+ end
+
+ def email_address
+ @applicant['contactInfo']['email']
+ end
+
+ def country
+ @applicant['country']
+ end
+
+ def state
+ @applicant['state']
+ end
+
+ def race_ethnicity
+ races = []
+ origin_race = @applicant['originRace']
+
+ races << 'American Indian or Alaska Native' if origin_race['isAmericanIndianOrAlaskanNative']
+ races << 'Asian' if origin_race['isAsian']
+ races << 'Black or African American' if origin_race['isBlackOrAfricanAmerican']
+ races << 'Native Hawaiian or Other Pacific Islander' if origin_race['isNativeHawaiianOrOtherPacificIslander']
+ races << 'White' if origin_race['isWhite']
+ races << 'Prefer Not to Answer' if origin_race['noAnswer']
+
+ return 'Not specified' if races.empty?
+
+ races.join(', ')
+ end
+
+ def gender
+ GENDER_TYPES[@applicant['gender']] || 'Not specified'
+ end
+
+ def education_level
+ EDUCATION_LEVELS[@applicant['highestLevelOfEducation']['level']] || 'Not specified'
+ end
+
+ def employment_status
+ @applicant['currentlyEmployed'] ? 'Yes' : 'No'
+ end
+
+ def salary
+ SALARY_TYPES[@applicant['currentAnnualSalary']&.to_sym] || 'Not specified'
+ end
+
+ def technology_industry
+ return 'No' unless @applicant['isWorkingInTechIndustry']
+
+ TECH_AREAS[@applicant['techIndustryFocusArea']&.to_sym] || 'Not specified'
+ end
+
+ def header_form_type
+ 'V10282'
+ end
+ end
+end
diff --git a/app/sidekiq/education_form/forms/va_1995.rb b/app/sidekiq/education_form/forms/va_1995.rb
index 508c3581168..4ac1c6b2267 100644
--- a/app/sidekiq/education_form/forms/va_1995.rb
+++ b/app/sidekiq/education_form/forms/va_1995.rb
@@ -26,7 +26,7 @@ def form_benefit
end
def header_form_type
- 'V1995'
+ @applicant.rudisillReview == 'Yes' ? '1995R' : 'V1995'
end
end
end
diff --git a/app/sidekiq/education_form/send_school_certifying_officials_email.rb b/app/sidekiq/education_form/send_school_certifying_officials_email.rb
index 16ec3d95628..d392f53451b 100644
--- a/app/sidekiq/education_form/send_school_certifying_officials_email.rb
+++ b/app/sidekiq/education_form/send_school_certifying_officials_email.rb
@@ -33,6 +33,9 @@ def self.sco_emails(scos)
def get_institution(facility_code)
GI::Client.new.get_institution_details_v0({ id: facility_code }).body[:data][:attributes]
+ rescue Common::Exceptions::RecordNotFound
+ StatsD.increment("#{stats_key}.skipped.institution_not_approved")
+ nil
end
def school_changed?
diff --git a/app/sidekiq/education_form/templates/1995.erb b/app/sidekiq/education_form/templates/1995.erb
index 3ea59e2d7ef..98b3b80e739 100644
--- a/app/sidekiq/education_form/templates/1995.erb
+++ b/app/sidekiq/education_form/templates/1995.erb
@@ -57,6 +57,8 @@ VA File Number: <%= value_or_na(@applicant.vaFileNumber) %>
Benefit Most Recently Received: <%= form_benefit %>
+Do you wish to request a 'Rudisill' review?: <%= @applicant.rudisillReview %>
+
Select Another Benefit: <%= @applicant.changeAnotherBenefit %>
Benefit Being Applied For: <%= @applicant.benefitAppliedFor&.titleize %>
diff --git a/app/sidekiq/evss/document_upload.rb b/app/sidekiq/evss/document_upload.rb
index c8486287b51..0d30598c0f7 100644
--- a/app/sidekiq/evss/document_upload.rb
+++ b/app/sidekiq/evss/document_upload.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'ddtrace'
+require 'datadog'
require 'timeout'
require 'logging/third_party_transaction'
require 'evss/failure_notification'
diff --git a/app/sidekiq/lighthouse/benefits_intake/delete_old_claims.rb b/app/sidekiq/lighthouse/benefits_intake/delete_old_claims.rb
new file mode 100644
index 00000000000..6037ac6415d
--- /dev/null
+++ b/app/sidekiq/lighthouse/benefits_intake/delete_old_claims.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'pension_burial/tag_sentry'
+
+module Lighthouse
+ module BenefitsIntake
+ class DeleteOldClaims
+ include Sidekiq::Job
+
+ sidekiq_options retry: false
+
+ EXPIRATION_TIME = 2.months
+
+ def perform
+ PensionBurial::TagSentry.tag_sentry
+
+ CentralMailClaim.joins(:central_mail_submission).where.not(
+ central_mail_submissions: { state: 'pending' }
+ ).where(
+ 'created_at < ?', EXPIRATION_TIME.ago
+ ).find_each(&:destroy!)
+ end
+ end
+ end
+end
diff --git a/app/sidekiq/lighthouse/benefits_intake/submit_central_form686c_job.rb b/app/sidekiq/lighthouse/benefits_intake/submit_central_form686c_job.rb
new file mode 100644
index 00000000000..e276dcfd44e
--- /dev/null
+++ b/app/sidekiq/lighthouse/benefits_intake/submit_central_form686c_job.rb
@@ -0,0 +1,283 @@
+# frozen_string_literal: true
+
+require 'central_mail/service'
+require 'benefits_intake_service/service'
+require 'pdf_utilities/datestamp_pdf'
+require 'pdf_info'
+require 'simple_forms_api_submission/metadata_validator'
+require 'dependents/monitor'
+
+module Lighthouse
+ module BenefitsIntake
+ class SubmitCentralForm686cJob
+ include Sidekiq::Job
+ include SentryLogging
+
+ FOREIGN_POSTALCODE = '00000'
+ FORM_ID = '686C-674'
+ FORM_ID_674 = '21-674'
+ STATSD_KEY_PREFIX = 'worker.submit_686c_674_backup_submission'
+ # retry for 2d 1h 47m 12s
+ # https://github.com/sidekiq/sidekiq/wiki/Error-Handling
+ RETRY = 16
+
+ attr_reader :claim, :form_path, :attachment_paths
+
+ class BenefitsIntakeResponseError < StandardError; end
+
+ def extract_uuid_from_central_mail_message(data)
+ data.body[/(?<=\[).*?(?=\])/].split(': ').last if data.body.present?
+ end
+
+ sidekiq_options retry: RETRY
+
+ sidekiq_retries_exhausted do |msg, _ex|
+ if Flipper.enabled?(:dependents_trigger_action_needed_email)
+ Lighthouse::BenefitsIntake::SubmitCentralForm686cJob.trigger_failure_events(msg)
+ end
+ end
+
+ def perform(saved_claim_id, encrypted_vet_info, encrypted_user_struct)
+ vet_info = JSON.parse(KmsEncrypted::Box.new.decrypt(encrypted_vet_info))
+ user_struct = JSON.parse(KmsEncrypted::Box.new.decrypt(encrypted_user_struct))
+ # if the 686c-674 has failed we want to call this central mail job (credit to submit_saved_claim_job.rb)
+ # have to re-find the claim and add the relevant veteran info
+ Rails.logger.info('Lighthouse::BenefitsIntake::SubmitCentralForm686cJob running!',
+ { user_uuid: user_struct['uuid'], saved_claim_id:, icn: user_struct['icn'] })
+ @claim = SavedClaim::DependencyClaim.find(saved_claim_id)
+ claim.add_veteran_info(vet_info)
+
+ get_files_from_claim
+ result = upload_to_lh
+ check_success(result, saved_claim_id, user_struct)
+ rescue => e
+ # if we fail, update the associated central mail record to failed and send the user the failure email
+ Rails.logger.warn('Lighthouse::BenefitsIntake::SubmitCentralForm686cJob failed!',
+ { user_uuid: user_struct['uuid'], saved_claim_id:, icn: user_struct['icn'], error: e.message }) # rubocop:disable Layout/LineLength
+ update_submission('failed')
+ raise
+ ensure
+ cleanup_file_paths
+ end
+
+ def upload_to_lh
+ Rails.logger.info({ message: 'SubmitCentralForm686cJob Lighthouse Initiate Submission Attempt',
+ claim_id: claim.id })
+ lighthouse_service = BenefitsIntakeService::Service.new(with_upload_location: true)
+ uuid = lighthouse_service.uuid
+ Rails.logger.info({ message: 'SubmitCentralForm686cJob Lighthouse Submission Attempt', claim_id: claim.id,
+ uuid: })
+ response = lighthouse_service.upload_form(
+ main_document: split_file_and_path(form_path),
+ attachments: attachment_paths.map(&method(:split_file_and_path)),
+ form_metadata: generate_metadata_lh
+ )
+ create_form_submission_attempt(uuid)
+
+ Rails.logger.info({ message: 'SubmitCentralForm686cJob Lighthouse Submission Successful', claim_id: claim.id,
+ uuid: })
+ response
+ end
+
+ def create_form_submission_attempt(intake_uuid)
+ FormSubmissionAttempt.transaction do
+ form_submission = FormSubmission.create(
+ form_type: claim.submittable_686? ? FORM_ID : FORM_ID_674,
+ saved_claim: claim,
+ user_account: UserAccount.find_by(icn: claim.parsed_form['veteran_information']['icn'])
+ )
+ FormSubmissionAttempt.create(form_submission:, benefits_intake_uuid: intake_uuid)
+ end
+ end
+
+ def get_files_from_claim
+ # process the main pdf record and the attachments as we would for a vbms submission
+ form_674_path = process_pdf(claim.to_pdf(form_id: FORM_ID_674), claim.created_at, FORM_ID_674) if claim.submittable_674? # rubocop:disable Layout/LineLength
+ form_686c_path = process_pdf(claim.to_pdf(form_id: FORM_ID), claim.created_at, FORM_ID) if claim.submittable_686? # rubocop:disable Layout/LineLength
+ @form_path = form_686c_path || form_674_path
+ @attachment_paths = claim.persistent_attachments.map { |pa| process_pdf(pa.to_pdf, claim.created_at) }
+ # Treat 674 as first attachment
+ attachment_paths.insert(0, form_674_path) if form_686c_path.present? && form_674_path.present?
+ end
+
+ def cleanup_file_paths
+ Common::FileHelpers.delete_file_if_exists(form_path)
+ attachment_paths.each { |p| Common::FileHelpers.delete_file_if_exists(p) }
+ end
+
+ def check_success(response, saved_claim_id, user_struct)
+ if response.success?
+ Rails.logger.info('Lighthouse::BenefitsIntake::SubmitCentralForm686cJob succeeded!',
+ { user_uuid: user_struct['uuid'], saved_claim_id:, icn: user_struct['icn'] })
+ update_submission('success')
+ send_confirmation_email(OpenStruct.new(user_struct))
+ else
+ Rails.logger.info('Lighthouse::BenefitsIntake::SubmitCentralForm686cJob Unsuccessful',
+ { response: response['message'].presence || response['errors'] })
+ raise BenefitsIntakeResponseError
+ end
+ end
+
+ def create_request_body
+ body = {
+ 'metadata' => generate_metadata.to_json
+ }
+
+ body['document'] = to_faraday_upload(form_path)
+
+ i = 0
+ attachment_paths.each do |file_path|
+ body["attachment#{i += 1}"] = to_faraday_upload(file_path)
+ end
+
+ body
+ end
+
+ def update_submission(state)
+ claim.central_mail_submission.update!(state:) if claim.respond_to?(:central_mail_submission)
+ end
+
+ def to_faraday_upload(file_path)
+ Faraday::UploadIO.new(
+ file_path,
+ Mime[:pdf].to_s
+ )
+ end
+
+ def process_pdf(pdf_path, timestamp = nil, form_id = nil)
+ stamped_path1 = PDFUtilities::DatestampPdf.new(pdf_path).run(text: 'VA.GOV', x: 5, y: 5, timestamp:)
+ stamped_path2 = PDFUtilities::DatestampPdf.new(stamped_path1).run(
+ text: 'FDC Reviewed - va.gov Submission',
+ x: 400,
+ y: 770,
+ text_only: true
+ )
+ if form_id.present?
+ stamped_pdf_with_form(form_id, stamped_path2, timestamp)
+ else
+ stamped_path2
+ end
+ end
+
+ def get_hash_and_pages(file_path)
+ {
+ hash: Digest::SHA256.file(file_path).hexdigest,
+ pages: PdfInfo::Metadata.read(file_path).pages
+ }
+ end
+
+ def generate_metadata # rubocop:disable Metrics/MethodLength
+ form = claim.parsed_form['dependents_application']
+ veteran_information = form['veteran_information'].presence || claim.parsed_form['veteran_information']
+ form_pdf_metadata = get_hash_and_pages(form_path)
+ address = form['veteran_contact_information']['veteran_address']
+ is_usa = address['country_name'] == 'USA'
+ metadata = {
+ 'veteranFirstName' => veteran_information['full_name']['first'],
+ 'veteranLastName' => veteran_information['full_name']['last'],
+ 'fileNumber' => veteran_information['file_number'] || veteran_information['ssn'],
+ 'receiveDt' => claim.created_at.in_time_zone('Central Time (US & Canada)').strftime('%Y-%m-%d %H:%M:%S'),
+ 'uuid' => claim.guid,
+ 'zipCode' => is_usa ? address['zip_code'] : FOREIGN_POSTALCODE,
+ 'source' => 'va.gov',
+ 'hashV' => form_pdf_metadata[:hash],
+ 'numberAttachments' => attachment_paths.size,
+ 'docType' => claim.form_id,
+ 'numberPages' => form_pdf_metadata[:pages]
+ }
+
+ validated_metadata = SimpleFormsApiSubmission::MetadataValidator.validate(
+ metadata,
+ zip_code_is_us_based: is_usa
+ )
+
+ validated_metadata.merge(generate_attachment_metadata(attachment_paths))
+ end
+
+ def generate_metadata_lh
+ form = claim.parsed_form['dependents_application']
+ # sometimes veteran_information is not in dependents_application, but claim.add_veteran_info will make sure
+ # it's in the outer layer of parsed_form
+ veteran_information = form['veteran_information'].presence || claim.parsed_form['veteran_information']
+ address = form['veteran_contact_information']['veteran_address']
+ {
+ veteran_first_name: veteran_information['full_name']['first'],
+ veteran_last_name: veteran_information['full_name']['last'],
+ file_number: veteran_information['file_number'] || veteran_information['ssn'],
+ zip: address['country_name'] == 'USA' ? address['zip_code'] : FOREIGN_POSTALCODE,
+ doc_type: claim.form_id,
+ claim_date: claim.created_at,
+ source: 'va.gov backup dependent claim submission',
+ business_line: 'CMP'
+ }
+ end
+
+ def generate_attachment_metadata(attachment_paths)
+ attachment_metadata = {}
+ i = 0
+ attachment_paths.each do |file_path|
+ i += 1
+ attachment_pdf_metadata = get_hash_and_pages(file_path)
+ attachment_metadata["ahash#{i}"] = attachment_pdf_metadata[:hash]
+ attachment_metadata["numberPages#{i}"] = attachment_pdf_metadata[:pages]
+ end
+ attachment_metadata
+ end
+
+ def send_confirmation_email(user)
+ return if user.va_profile_email.blank?
+
+ VANotify::ConfirmationEmail.send(
+ email_address: user.va_profile_email,
+ template_id: Settings.vanotify.services.va_gov.template_id.form686c_confirmation_email,
+ first_name: user&.first_name&.upcase,
+ user_uuid_and_form_id: "#{user.uuid}_#{FORM_ID}"
+ )
+ end
+
+ def self.trigger_failure_events(msg)
+ monitor = Dependents::Monitor.new
+ saved_claim_id, _, encrypted_user_struct = msg['args']
+ user_struct = JSON.parse(KmsEncrypted::Box.new.decrypt(encrypted_user_struct)) if encrypted_user_struct.present?
+ claim = SavedClaim::DependencyClaim.find(saved_claim_id)
+ email = claim.parsed_form.dig('dependents_application', 'veteran_contact_information', 'email_address') ||
+ user_struct.try(:va_profile_email)
+ monitor.track_submission_exhaustion(msg, email)
+ claim.send_failure_email(email)
+ end
+
+ private
+
+ def stamped_pdf_with_form(form_id, path, timestamp)
+ PDFUtilities::DatestampPdf.new(path).run(
+ text: 'Application Submitted on va.gov',
+ x: form_id == '686C-674' ? 400 : 300,
+ y: form_id == '686C-674' ? 675 : 775,
+ text_only: true, # passing as text only because we override how the date is stamped in this instance
+ timestamp:,
+ page_number: form_id == '686C-674' ? 6 : 0,
+ template: "lib/pdf_fill/forms/pdfs/#{form_id}.pdf",
+ multistamp: true
+ )
+ end
+
+ def log_cmp_response(response)
+ log_message_to_sentry("vre-central-mail-response: #{response}", :info, {}, { team: 'vfs-ebenefits' })
+ end
+
+ def valid_claim_data(saved_claim_id, vet_info)
+ claim = SavedClaim::DependencyClaim.find(saved_claim_id)
+
+ claim.add_veteran_info(vet_info)
+
+ raise Invalid686cClaim unless claim.valid?(:run_686_form_jobs)
+
+ claim.formatted_686_data(vet_info)
+ end
+
+ def split_file_and_path(path)
+ { file: path, file_name: path.split('/').last }
+ end
+ end
+ end
+end
diff --git a/app/sidekiq/lighthouse/document_upload.rb b/app/sidekiq/lighthouse/document_upload.rb
index 084cf600e33..4ac67ff7e1c 100644
--- a/app/sidekiq/lighthouse/document_upload.rb
+++ b/app/sidekiq/lighthouse/document_upload.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'ddtrace'
+require 'datadog'
require 'timeout'
require 'lighthouse/benefits_documents/worker_service'
require 'lighthouse/failure_notification'
diff --git a/app/sidekiq/lighthouse/document_upload_synchronous.rb b/app/sidekiq/lighthouse/document_upload_synchronous.rb
index 2975e0581dc..a1bb4a54880 100644
--- a/app/sidekiq/lighthouse/document_upload_synchronous.rb
+++ b/app/sidekiq/lighthouse/document_upload_synchronous.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'ddtrace'
+require 'datadog'
require 'timeout'
require 'lighthouse/benefits_documents/worker_service'
diff --git a/app/sidekiq/lighthouse/income_and_assets_intake_job.rb b/app/sidekiq/lighthouse/income_and_assets_intake_job.rb
index d25f074b82c..6dd1dc22be5 100644
--- a/app/sidekiq/lighthouse/income_and_assets_intake_job.rb
+++ b/app/sidekiq/lighthouse/income_and_assets_intake_job.rb
@@ -73,7 +73,7 @@ def init(saved_claim_id, user_account_uuid)
@claim = SavedClaim::IncomeAndAssets.find(saved_claim_id)
raise IncomeAndAssetsIntakeError, "Unable to find SavedClaim::IncomeAndAssets #{saved_claim_id}" unless @claim
- @intake_service = BenefitsIntake::Service.new
+ @intake_service = ::BenefitsIntake::Service.new
end
##
@@ -108,7 +108,7 @@ def generate_metadata
form = @claim.parsed_form
# also validates/maniuplates the metadata
- BenefitsIntake::Metadata.generate(
+ ::BenefitsIntake::Metadata.generate(
form['veteranFullName']['first'],
form['veteranFullName']['last'],
form['vaFileNumber'] || form['veteranSocialSecurityNumber'],
diff --git a/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb b/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb
index 226ebde8f3b..f42a0b39e49 100644
--- a/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb
+++ b/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb
@@ -7,7 +7,7 @@
require 'burials/notification_email'
require 'pcpg/monitor'
require 'benefits_intake_service/service'
-require 'simple_forms_api_submission/metadata_validator'
+require 'lighthouse/benefits_intake/metadata'
require 'pdf_info'
module Lighthouse
@@ -82,7 +82,7 @@ def generate_metadata
address = form['claimantAddress'] || form['veteranAddress']
# also validates/manipulates the metadata
- BenefitsIntake::Metadata.generate(
+ ::BenefitsIntake::Metadata.generate(
veteran_full_name['first'],
veteran_full_name['last'],
form['vaFileNumber'] || form['veteranSocialSecurityNumber'],
diff --git a/app/uploaders/form_upload/uploader.rb b/app/uploaders/form_upload/uploader.rb
index c49c57c6788..d1a6f198ce8 100644
--- a/app/uploaders/form_upload/uploader.rb
+++ b/app/uploaders/form_upload/uploader.rb
@@ -14,7 +14,7 @@ class FormUpload::Uploader < VetsShrine
Attacher.validate do
validate_virus_free
- validate_max_size 20.megabytes
+ validate_max_size 25.megabytes
validate_min_size 1.kilobyte
validate_mime_type_inclusion %w[image/jpg image/jpeg image/png application/pdf]
validate_max_width 5000 if get.width
diff --git a/config/betamocks/services_config.yml b/config/betamocks/services_config.yml
index 3e29c582c21..48fa1389752 100644
--- a/config/betamocks/services_config.yml
+++ b/config/betamocks/services_config.yml
@@ -9,25 +9,37 @@
:path: <%= "/#{Settings.ask_va_api.crm_api.veis_api_path}/ping" %>
:file_path: "/ask_va/dynamics_api"
:response_delay: 15
- - :method: :get
- :path: "/veis/api/btsss/travelclaim/api/v1/Sample/ping"
- :file_path: "/travel_pay/ping/default"
- :response_delay: 0.3
- - :method: :get
- :path: "/veis/api/btsss/travelclaim/api/v1/Sample/authorized-ping"
- :file_path: "/travel_pay/ping/default"
+ - :method: :post
+ :path: <%= "/#{Settings.ask_va_api.crm_api.veis_api_path}/inquiries/new" %>
+ :file_path: "/ask_va/crm_api/post_inquiries/default"
:response_delay: 0.3
+ ## Travel Pay
- :method: :post
- :path: "/veis/api/btsss/travelclaim/api/v1/Auth/access-token"
+ :path: "/veis/api/btsss/travelclaim/api/v1.2/Auth/access-token"
:file_path: "/travel_pay/btsss_token/default"
:response_delay: 0.3
- :method: :get
- :path: "/veis/api/btsss/travelclaim/api/v1/claims"
- :file_path: "/travel_pay/claims/default"
+ :path: "/veis/api/btsss/travelclaim/api/v1.2/claims"
+ :file_path: "/travel_pay/claims/index/default"
+ :response_delay: 0.3
+ - :method: :get
+ :path: "/veis/api/btsss/travelclaim/api/v1.2/appointments"
+ :file_path: "/travel_pay/appointments/default"
:response_delay: 0.3
+ - :method: :patch
+ :path: "/veis/api/btsss/travelclaim/api/v1.2/claims/*/submit"
+ :file_path: "/travel_pay/claims/submit"
+ :response_delay: 0.3
+ :cache_multiple_responses:
+ :uid_location: url
+ :uid_locator: '\/veis\/api\/btsss\/travelclaim\/api\/v1\.2\/claims\/(.+)\/submit'
- :method: :post
- :path: <%= "/#{Settings.ask_va_api.crm_api.veis_api_path}/inquiries/new" %>
- :file_path: "/ask_va/crm_api/post_inquiries/default"
+ :path: "/veis/api/btsss/travelclaim/api/v1.2/claims"
+ :file_path: "/travel_pay/claims/create/default"
+ :response_delay: 0.3
+ - :method: :post
+ :path: "/veis/api/btsss/travelclaim/api/v1.2/expenses/mileage"
+ :file_path: "/travel_pay/expenses/default"
:response_delay: 0.3
#CRM Token post
diff --git a/config/features.yml b/config/features.yml
index 0cada8cac3e..6985f24e133 100644
--- a/config/features.yml
+++ b/config/features.yml
@@ -67,7 +67,7 @@ features:
enable_in_development: true
benefits_require_gateway_origin:
actor_type: user
- description: Requires that all requests made to endpoints in appeals_api, va_forms, and vba_documents be made through the gateway
+ description: Requires that all requests made to endpoints in appeals_api and vba_documents be made through the gateway
caregiver_use_facilities_API:
actor_type: user
description: Allow list of caregiver facilites to be fetched by way of the Facilities API.
@@ -355,9 +355,6 @@ features:
communication_preferences:
actor_type: user
description: Allow user to access backend communication_preferences API
- contact_info_change_email:
- actor_type: user
- description: Send user a notification email when their contact info changes.
covid_vaccine_registration:
actor_type: user
description: Toggles availability of covid vaccine form API.
@@ -417,6 +414,12 @@ features:
claims_status_v1_lh_auto_establish_claim_enabled:
actor_type: user
description: With feature flag enabled, v1 /526 should use Lighthouse Form526 docker container
+ cst_send_evidence_submission_failure_emails:
+ actor_type: user
+ description: >
+ If enabled and a user submits an evidence submission upload that fails to send, an email will be sent to the user and retried.
+ When disabled and a user submits an evidence submission upload that fails to send, an email will be sent to the user and not retried.
+ enable_in_development: true
debt_letters_show_letters_vbms:
actor_type: user
description: Enables debt letter download from VBMS
@@ -788,6 +791,9 @@ features:
form21_4142_confirmation_email:
actor_type: user
description: Enables form 21-4142 email submission confirmation (VaNotify)
+ form22_10282_confirmation_email:
+ actor_type: user
+ description: Enables form 22-10282 email submission confirmation (VaNotify)
enable_in_development: true
form26_4555_confirmation_email:
actor_type: user
@@ -865,6 +871,9 @@ features:
form1010d:
actor_type: cookie_id
description: If enabled shows the digital form experience for form 10-10d (IVC CHAMPVA)
+ form1010d_browser_monitoring_enabled:
+ actor_type: user
+ description: Datadog RUM monitoring for form 10-10d (IVC CHAMPVA)
form107959c:
actor_type: user
description: If enabled shows the digital form experience for form 10-7959c (IVC CHAMPVA other health insurance)
@@ -1366,6 +1375,14 @@ features:
actor_type: user
description: enables exclusion period checks
enable_in_development: false
+ meb_dpo_address_option_enabled:
+ actor_type: user
+ description: enables DPO option on address field
+ enable_in_development: false
+ meb_kicker_notification_enabled:
+ actor_type: user
+ description: enables kicker notification on additional consideration questions
+ enable_in_development: false
meb_auto_populate_relinquishment_date:
actor_type: user
description: Flag to autofill datepicker for reliinquishment date
@@ -1822,6 +1839,13 @@ features:
burial_form_enabled:
actor_type: user
description: Enable the burial form
+ burial_module_enabled:
+ actor_type: user
+ description: Enables new Burial module
+ enable_in_development: true
+ burial_document_upload_update:
+ actor_type: user
+ description: Show updated document upload page
burial_confirmation_page:
actor_type: user
description: Toggle showing the updated confirmation page
@@ -1950,7 +1974,10 @@ features:
enabled_in_development: true
remove_pciu:
actor_type: user
- description: If enabled, VA Profile is used to populate contact information- without PCIU calls (status quo)
+ description: If enabled, VA Profile is used to populate contact information with PCIU backup calls
+ remove_pciu_2:
+ actor_type: user
+ description: If enabled, removes all PCIU requests in form pre-fill.
show_yellow_ribbon_table:
actor_type: user
description: Used to show yellow ribbon table in Comparison Tool
@@ -1994,5 +2021,6 @@ features:
show_rudisill_1995:
actor_type: user
description: If enabled, show rudisill review in 22-1995
-
-
+ enable_lighthouse:
+ actor_type: user
+ description: If enabled, user will connect to lighthouse api in sob instead of evss
diff --git a/config/initializers/datadog.rb b/config/initializers/datadog.rb
index d020a5488f7..a3880058ede 100644
--- a/config/initializers/datadog.rb
+++ b/config/initializers/datadog.rb
@@ -33,9 +33,5 @@
# Enable ASM
c.appsec.enabled = true
c.appsec.instrument :rails
-
- elsif Settings.vsp_environment == 'test'
- # Set transport to no-op mode. Does not retain traces.
- c.tracing.transport_options = ->(t) { t.adapter :test }
end
end
diff --git a/config/redis.yml b/config/redis.yml
index eafdbea4800..936919887c8 100644
--- a/config/redis.yml
+++ b/config/redis.yml
@@ -66,9 +66,6 @@ development: &defaults
namespace: mpi-profile-response
each_ttl: 86400
failure_ttl: 1800
- okta_response:
- namespace: okta-response
- each_ttl: 3600
profile:
namespace: profile
each_ttl: 3600
@@ -78,9 +75,6 @@ development: &defaults
charon_response:
namespace: charon-response
each_ttl: 3600
- okta_response_app:
- namespace: okta-response
- each_ttl: 86400
saml_store:
namespace: single-logout-request
each_ttl: 43200
diff --git a/config/routes.rb b/config/routes.rb
index 6991a60729b..fa35f7ccdff 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -130,7 +130,7 @@
resources :caregivers_assistance_claims, only: :create do
collection do
- get(:facilities)
+ post(:facilities)
post(:download_pdf)
end
end
@@ -461,7 +461,6 @@
mount AppealsApi::Engine, at: '/appeals'
mount ClaimsApi::Engine, at: '/claims'
mount Veteran::Engine, at: '/veteran'
- mount VAForms::Engine, at: '/va_forms'
mount VeteranConfirmation::Engine, at: '/veteran_confirmation'
end
diff --git a/config/settings.yml b/config/settings.yml
index 8beba3490e5..88d47b14350 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -230,6 +230,17 @@ edu:
- marelby.hernandez@va.gov
- nawar.hussein@va.gov
- engin.akman@va.gov
+ staging_excel_contents:
+ emails:
+ - alex.chan1@va.gov
+ - gregg.puhala@va.gov
+ - noah.stern@va.gov
+ - marelby.hernandez@va.gov
+ - nawar.hussein@va.gov
+ - engin.akman@va.gov
+ production_excel_contents:
+ emails:
+ - patricia.terry1@va.gov
dependents:
prefill: true
@@ -401,6 +412,8 @@ intent_to_file:
ivc_champva:
prefill: true
+ pega_api:
+ api_key: fake_api_key
form_upload_flow:
prefill: true
@@ -1414,7 +1427,7 @@ vanotify:
template_id: burial_claim_error_email_template_id
flipper_id: burial_error_email_notification
received:
- template_id: null
+ template_id: burial_received_email_template_id
flipper_id: burial_received_email_notification
burials: *vanotify_services_burial
21p_530: *vanotify_services_burial
@@ -1426,19 +1439,6 @@ vanotify:
status_callback:
bearer_token: fake_bearer_token
-# Settings to connect to the drupal forms graphql api
-va_forms:
- drupal_username: ~
- drupal_password: ~
- drupal_url: https://fake-url.com
- form_reloader:
- enabled: false
- # A notification for when a forms url has changed
- slack:
- enabled: false
- api_key: ""
- channel_id: ""
-
# Settings for connecting to genISIS, this is the storage system for the COVID Research initiative
genisis:
base_url: https://vaausapprne60.aac.dva.va.gov
@@ -1868,7 +1868,7 @@ ogc:
accredited_representative_portal:
pilot_users_email_poa_codes: ~
-
+
banners:
drupal_username: banners_api
drupal_password: test
diff --git a/config/settings/test.yml b/config/settings/test.yml
index 35f28fbb40a..319dda4dc4c 100644
--- a/config/settings/test.yml
+++ b/config/settings/test.yml
@@ -127,11 +127,6 @@ flipper:
github_oauth_key: xxx000
github_oauth_secret: 000xxx
-va_forms:
- drupal_username: ~
- drupal_password: ~
- drupal_url: https://fake-url.com
-
vanotify:
client_url: http://fakeapi.com
services:
diff --git a/db/migrate/20241218183417_add_proc_id_index_to_claims_api_power_of_attorney_requests.rb b/db/migrate/20241218183417_add_proc_id_index_to_claims_api_power_of_attorney_requests.rb
new file mode 100644
index 00000000000..ea7a4e8908d
--- /dev/null
+++ b/db/migrate/20241218183417_add_proc_id_index_to_claims_api_power_of_attorney_requests.rb
@@ -0,0 +1,7 @@
+class AddProcIdIndexToClaimsApiPowerOfAttorneyRequests < ActiveRecord::Migration[7.0]
+ disable_ddl_transaction!
+
+ def change
+ add_index :claims_api_power_of_attorney_requests, :proc_id, algorithm: :concurrently
+ end
+end
diff --git a/db/migrate/20241219205816_create_excel_file_events.rb b/db/migrate/20241219205816_create_excel_file_events.rb
new file mode 100644
index 00000000000..ba7a44f32db
--- /dev/null
+++ b/db/migrate/20241219205816_create_excel_file_events.rb
@@ -0,0 +1,12 @@
+class CreateExcelFileEvents < ActiveRecord::Migration[7.2]
+ def change
+ create_table :excel_file_events do |t|
+ t.integer :number_of_submissions
+ t.string :filename
+ t.timestamp :successful_at
+ t.integer :retry_attempt, default: 0
+ t.timestamps
+ t.index :filename, name: "index_excel_file_events_uniqueness", unique: true
+ end
+ end
+end
diff --git a/db/migrate/20241231213045_drop_va_forms_forms.rb b/db/migrate/20241231213045_drop_va_forms_forms.rb
new file mode 100644
index 00000000000..d00a367efed
--- /dev/null
+++ b/db/migrate/20241231213045_drop_va_forms_forms.rb
@@ -0,0 +1,5 @@
+class DropVAFormsForms < ActiveRecord::Migration[7.2]
+ def change
+ drop_table :va_forms_forms, if_exists: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 357f83dd6a3..df67bda7d8e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2024_12_20_164548) do
+ActiveRecord::Schema[7.2].define(version: 2024_12_31_213045) do
# These are extensions that must be enabled in order to support this database
enable_extension "btree_gin"
enable_extension "fuzzystrmatch"
@@ -273,12 +273,14 @@
t.uuid "power_of_attorney_request_id", null: false
t.text "encrypted_kms_key"
t.text "data_ciphertext", null: false
- t.string "city_bidx", null: false
- t.string "state_bidx", null: false
- t.string "zipcode_bidx", null: false
- t.index ["city_bidx", "state_bidx", "zipcode_bidx"], name: "idx_on_city_bidx_state_bidx_zipcode_bidx_a85b76f9bc"
+ t.string "claimant_city_ciphertext", null: false
+ t.string "claimant_city_bidx", null: false
+ t.string "claimant_state_code_ciphertext", null: false
+ t.string "claimant_state_code_bidx", null: false
+ t.string "claimant_zip_code_ciphertext", null: false
+ t.string "claimant_zip_code_bidx", null: false
+ t.index ["claimant_city_bidx", "claimant_state_code_bidx", "claimant_zip_code_bidx"], name: "idx_on_claimant_city_bidx_claimant_state_code_bidx__11e9adbe25"
t.index ["power_of_attorney_request_id"], name: "idx_on_power_of_attorney_request_id_fc59a0dabc", unique: true
- t.index ["zipcode_bidx"], name: "index_ar_power_of_attorney_forms_on_zipcode_bidx"
end
create_table "ar_power_of_attorney_request_decisions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
@@ -304,6 +306,7 @@
create_table "ar_power_of_attorney_requests", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "claimant_id", null: false
t.datetime "created_at", null: false
+ t.string "claimant_type", null: false
t.index ["claimant_id"], name: "index_ar_power_of_attorney_requests_on_claimant_id"
end
@@ -466,6 +469,7 @@
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["power_of_attorney_id"], name: "idx_on_power_of_attorney_id_9fc9134311"
+ t.index ["proc_id"], name: "index_claims_api_power_of_attorney_requests_on_proc_id"
end
create_table "claims_api_power_of_attorneys", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
@@ -693,6 +697,16 @@
t.index ["user_uuid"], name: "index_evss_claims_on_user_uuid"
end
+ create_table "excel_file_events", force: :cascade do |t|
+ t.integer "number_of_submissions"
+ t.string "filename"
+ t.datetime "successful_at", precision: nil
+ t.integer "retry_attempt", default: 0
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["filename"], name: "index_excel_file_events_uniqueness", unique: true
+ end
+
create_table "feature_toggle_events", force: :cascade do |t|
t.string "feature_name"
t.string "operation"
@@ -1388,35 +1402,6 @@
t.index ["verified_at"], name: "index_user_verifications_on_verified_at"
end
- create_table "va_forms_forms", force: :cascade do |t|
- t.string "form_name"
- t.string "url"
- t.string "title"
- t.date "first_issued_on"
- t.date "last_revision_on"
- t.integer "pages"
- t.string "sha256"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.boolean "valid_pdf", default: false
- t.text "form_usage"
- t.text "form_tool_intro"
- t.string "form_tool_url"
- t.string "form_type"
- t.string "language"
- t.datetime "deleted_at"
- t.string "related_forms", array: true
- t.jsonb "benefit_categories"
- t.string "form_details_url"
- t.jsonb "va_form_administration"
- t.integer "row_id"
- t.float "ranking"
- t.string "tags"
- t.date "last_sha256_change"
- t.jsonb "change_history"
- t.index ["valid_pdf"], name: "index_va_forms_forms_on_valid_pdf"
- end
-
create_table "va_notify_in_progress_reminders_sent", force: :cascade do |t|
t.string "form_id", null: false
t.uuid "user_account_id", null: false
diff --git a/docs/setup/va_forms.md b/docs/setup/va_forms.md
deleted file mode 100644
index bc6d92d0068..00000000000
--- a/docs/setup/va_forms.md
+++ /dev/null
@@ -1,56 +0,0 @@
-## VA Forms
-
-The VA Forms API is a service that synchronises form data from va.gov's
-Drupal CMS system. The CMS system syncs data from the VA source of truth
-(Forms DB) nightly between 12AM and 1 AM. The form_reloader.rb SideKiq job then
-syncs the CMS content to our local Postgres DB.
-To configure `vets-api` for use with VA Forms, configure
-`config/settings.local.yml` with the settings given to you by devops or your
-team. For example,
-
-```
-# config/settings.local.yml
-va_forms:
- drupal_username:
- drupal_password:
- drupal_url:
-```
-
-Since the CMS URL is only accessible over SOCKS, ensure that you have SOCKS properly
-configured and running if populating data locally, using form_reloader.rb
-
-To troubleshoot differences between the vets-api version of a form and the
-Drupal CMS version, you may log into the Drupal explorer on socks and run
-GraphQL queries against it. Contact the CMS team for a login.
-
-A sample query to find the form 10-10EZ record in Drupal:
-
-```
-{
- nodeQuery(
- limit: 1000
- offset: 0
- filter: {
- conditions: [
- { field: "field_va_form_number", value: "10-10ez%", operator: LIKE }
- ]
- }
- ) {
- entities {
- entityId
- entityBundle
- ... on NodeVaForm {
- fieldVaFormNumber
- fieldVaFormName
- fieldVaFormTitle
- fieldVaFormDeleted
- fieldVaFormToolUrl {
- uri
- title
- options
- }
- }
- }
- }
-}
-```
\ No newline at end of file
diff --git a/lib/common/client/concerns/monitoring.rb b/lib/common/client/concerns/monitoring.rb
index 57cf80b53f1..248753acf33 100644
--- a/lib/common/client/concerns/monitoring.rb
+++ b/lib/common/client/concerns/monitoring.rb
@@ -7,7 +7,7 @@ module Monitoring
extend ActiveSupport::Concern
def with_monitoring(trace_location = 1)
- caller = caller_locations(trace_location, 1)[0].label
+ caller = caller_locations(trace_location, 1)[0].base_label
yield
rescue => e
increment_failure(caller, e)
diff --git a/lib/dependents/monitor.rb b/lib/dependents/monitor.rb
index abe75ee283a..509688097ab 100644
--- a/lib/dependents/monitor.rb
+++ b/lib/dependents/monitor.rb
@@ -38,7 +38,8 @@ def track_submission_exhaustion(msg, email = nil)
StatsD.increment("#{SUBMISSION_STATS_KEY}.exhausted")
Rails.logger.error(
- "Failed all retries on CentralMail::SubmitCentralForm686cJob, last error: #{msg['error_message']}"
+ 'Failed all retries on Lighthouse::BenefitsIntake::SubmitCentralForm686cJob, ' \
+ "last error: #{msg['error_message']}"
)
end
end
diff --git a/lib/evss/disability_compensation_form/form0781.rb b/lib/evss/disability_compensation_form/form0781.rb
index d2d870ed2ee..72adae303b1 100644
--- a/lib/evss/disability_compensation_form/form0781.rb
+++ b/lib/evss/disability_compensation_form/form0781.rb
@@ -58,6 +58,7 @@ def create_form_v2
'reports' => @form_content['reports'],
'reportsDetails' => @form_content['reportsDetails'],
'behaviors' => @form_content['behaviors'],
+ 'otherBehaviors' => @form_content['otherBehaviors'],
'behaviorsDetails' => @form_content['behaviorsDetails'],
'evidence' => @form_content['evidence'],
'traumaTreatment' => @form_content['traumaTreatment'],
diff --git a/lib/form1010_ezr/service.rb b/lib/form1010_ezr/service.rb
index 84d7dea6960..5b527976358 100644
--- a/lib/form1010_ezr/service.rb
+++ b/lib/form1010_ezr/service.rb
@@ -5,6 +5,7 @@
require 'hca/configuration'
require 'hca/ezr_postfill'
require 'va1010_forms/utils'
+require 'hca/overrides_parser'
module Form1010Ezr
class Service < Common::Client::Base
@@ -155,7 +156,7 @@ def configure_and_validate_form(parsed_form)
post_fill_fields(parsed_form)
validate_form(parsed_form)
# Due to overriding the JSON form schema, we need to do so after the form has been validated
- override_parsed_form(parsed_form)
+ HCA::OverridesParser.new(parsed_form).override
add_financial_flag(parsed_form)
end
diff --git a/lib/hca/enrollment_system.rb b/lib/hca/enrollment_system.rb
index ecff838ea52..a0186c137fb 100644
--- a/lib/hca/enrollment_system.rb
+++ b/lib/hca/enrollment_system.rb
@@ -865,18 +865,18 @@ def add_attachment(file, id, is_dd214)
end
# @param [Hash] veteran data in JSON format
- # @param [Account] current_user
+ # @param [Hash] user_identifier
# @param [String] form_id
def veteran_to_save_submit_form(
veteran,
- current_user,
+ user_identifier,
form_id
)
return {} if veteran.blank?
copy_spouse_address!(veteran)
- request = build_form_for_user(current_user, form_id)
+ request = build_form_for_user(user_identifier, form_id)
veteran['attachments']&.each_with_index do |attachment, i|
guid = attachment['confirmationCode']
diff --git a/lib/mpi/errors/errors.rb b/lib/mpi/errors/errors.rb
index e7cbd9f76f6..c46208ec558 100644
--- a/lib/mpi/errors/errors.rb
+++ b/lib/mpi/errors/errors.rb
@@ -16,6 +16,7 @@ class InvalidResponseParamsError < StandardError; end
class RecordNotFound < MPI::Errors::Response; end
class ArgumentError < MPI::Errors::Response; end
class DuplicateRecords < MPI::Errors::Response; end
+ class AccountLockedError < StandardError; end
class Request < ServiceError; end
class FailedRequestError < MPI::Errors::Request; end
class InvalidRequestError < MPI::Errors::Request; end
diff --git a/lib/pdf_fill/forms/va210781v2.rb b/lib/pdf_fill/forms/va210781v2.rb
index 24528c9cfe5..eedcbbd0477 100644
--- a/lib/pdf_fill/forms/va210781v2.rb
+++ b/lib/pdf_fill/forms/va210781v2.rb
@@ -619,6 +619,7 @@ def merge_fields(_options = {})
set_treatment_selection
set_reports_selection
+ set_option_indicator
format_other_behavior_details
format_police_report_location
@@ -674,8 +675,18 @@ def set_reports_selection
@form_data['otherReport'] = reports['other'] ? 4 : nil
end
+ def set_option_indicator
+ selected_option = @form_data['optionIndicator']
+ valid_options = %w[yes no revoke notEnrolled]
+
+ return if selected_option.nil? || valid_options.exclude?(selected_option)
+
+ @form_data['optionIndicator'] = valid_options.index_with { |_option| false }
+ @form_data['optionIndicator'][selected_option] = true
+ end
+
def format_other_behavior_details
- other_behavior = @form_data['behaviors']&.[]('otherBehavior')
+ other_behavior = @form_data['otherBehaviors']
return if other_behavior.blank?
details = @form_data['behaviorsDetails']['otherBehavior']
diff --git a/lib/periodic_jobs.rb b/lib/periodic_jobs.rb
index bd02b1c5313..178cb38315d 100644
--- a/lib/periodic_jobs.rb
+++ b/lib/periodic_jobs.rb
@@ -50,8 +50,8 @@
# Checks status of Flipper features expected to be enabled and alerts to Slack if any are not enabled
mgr.register('0 2,9,16 * * 1-5', 'AppealsApi::FlipperStatusAlert')
- # Update alternative Banners data every 10 minutes
- mgr.register('*/10 * * * *', 'Banners::UpdateAllJob')
+ # Update alternative Banners data every 5 minutes
+ mgr.register('*/5 * * * *', 'Banners::UpdateAllJob')
# Update static data cache
mgr.register('0 0 * * *', 'Crm::TopicsDataJob')
@@ -115,6 +115,7 @@
# TODO: Document this job
mgr.register('0 3 * * MON-FRI', 'EducationForm::CreateDailySpoolFiles')
+ mgr.register('0 3 * * MON-FRI', 'EducationForm::CreateDailyExcelFiles')
# Deletes old, completed AsyncTransaction records
mgr.register('0 3 * * *', 'DeleteOldTransactionsJob')
@@ -161,12 +162,6 @@
# TODO: Document this job
mgr.register('30 2 * * *', 'Identity::UserAcceptableVerifiedCredentialTotalsJob')
- # Fetches latest VA forms from Drupal database and updates vets-api forms database
- mgr.register('0 2 * * *', 'VAForms::FormReloader')
-
- # Checks status of Flipper features expected to be enabled and alerts to Slack if any are not enabled
- mgr.register('0 2,9,16 * * 1-5', 'VAForms::FlipperStatusAlert')
-
# TODO: Document these jobs
mgr.register('0 16 * * *', 'VANotify::InProgressForms')
mgr.register('0 1 * * *', 'VANotify::ClearStaleInProgressRemindersSent')
@@ -230,25 +225,14 @@
# Every 15min job that sends missing Pega statuses to DataDog
mgr.register('*/15 * * * *', 'IvcChampva::MissingFormStatusJob')
- # Hourly jobs that update DR SavedClaims with delete_date
- mgr.register('20 * * * *', 'DecisionReview::HlrStatusUpdaterJob')
- mgr.register('30 * * * *', 'DecisionReview::NodStatusUpdaterJob')
- mgr.register('50 * * * *', 'DecisionReview::ScStatusUpdaterJob')
-
# Engine version: Hourly jobs that update DR SavedClaims with delete_date
mgr.register('10 * * * *', 'DecisionReviews::HlrStatusUpdaterJob')
mgr.register('15 * * * *', 'DecisionReviews::NodStatusUpdaterJob')
mgr.register('40 * * * *', 'DecisionReviews::ScStatusUpdaterJob')
- # Clean SavedClaim records that are past delete date
- mgr.register('0 7 * * *', 'DecisionReview::DeleteSavedClaimRecordsJob')
-
# Engine version: Clean SavedClaim records that are past delete date
mgr.register('0 5 * * *', 'DecisionReviews::DeleteSavedClaimRecordsJob')
- # Send Decision Review emails to Veteran for failed form/evidence submissions
- mgr.register('5 1 * * *', 'DecisionReview::FailureNotificationEmailJob')
-
# Engine version: Send Decision Review emails to Veteran for failed form/evidence submissions
mgr.register('5 0 * * *', 'DecisionReviews::FailureNotificationEmailJob')
diff --git a/lib/shrine/plugins/validate_virus_free.rb b/lib/shrine/plugins/validate_virus_free.rb
index 9a4cf6aca19..1e89c2ddd91 100644
--- a/lib/shrine/plugins/validate_virus_free.rb
+++ b/lib/shrine/plugins/validate_virus_free.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'common/virus_scan'
-require 'ddtrace'
+require 'datadog'
class Shrine
module Plugins
diff --git a/lib/va1010_forms/enrollment_system/service.rb b/lib/va1010_forms/enrollment_system/service.rb
new file mode 100644
index 00000000000..87ec66ffc73
--- /dev/null
+++ b/lib/va1010_forms/enrollment_system/service.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'benchmark'
+require 'common/client/base'
+require 'hca/configuration'
+require 'hca/overrides_parser'
+
+module VA1010Forms
+ module EnrollmentSystem
+ class Service < Common::Client::Base
+ include ActionView::Helpers::NumberHelper
+
+ configuration HCA::Configuration
+
+ # @param [Hash] user_identifier
+ # @example { 'icn' => user.icn, 'edipi' => user.edipi }
+ def initialize(user_identifier = nil)
+ super()
+ @user_identifier = user_identifier
+ end
+
+ def submit(parsed_form, form_id)
+ formatted = HCA::EnrollmentSystem.veteran_to_save_submit_form(
+ parsed_form,
+ @user_identifier,
+ form_id
+ )
+ submission_body = submission_body(formatted)
+ response = perform(:post, '', submission_body)
+
+ root = response.body.locate('S:Envelope/S:Body/submitFormResponse').first
+ form_submission_id = root.locate('formSubmissionId').first.text.to_i
+
+ {
+ success: true,
+ formSubmissionId: form_submission_id,
+ timestamp: root.locate('timeStamp').first&.text || Time.now.getlocal.to_s
+ }
+ rescue => e
+ Rails.logger.error "#{form_id} form submission failed: #{e.message}"
+ raise e
+ end
+
+ private
+
+ def soap
+ # Savon *seems* like it should be setting these things correctly
+ # from what the docs say. Our WSDL file is weird, maybe?
+ Savon.client(
+ wsdl: HCA::Configuration::WSDL,
+ env_namespace: :soap,
+ element_form_default: :qualified,
+ namespaces: {
+ 'xmlns:tns': 'http://va.gov/service/esr/voa/v1'
+ },
+ namespace: 'http://va.gov/schema/esr/voa/v1'
+ )
+ end
+
+ def log_payload_info(formatted_form, submission_body)
+ form_name = formatted_form.dig('va:form', 'va:formIdentifier', 'va:value')
+ attachments = formatted_form.dig('va:form', 'va:attachments')
+ attachment_count = attachments&.length || 0
+ # Log the attachment sizes in descending order
+ if attachment_count.positive?
+ # Convert the attachments into xml format so they resemble what will be sent to VES
+ attachment_sizes =
+ attachments.map { |a| a.to_xml.size }.sort.reverse!.map { |size| number_to_human_size(size) }.join(', ')
+
+ Rails.logger.info("Attachment sizes in descending order: #{attachment_sizes}")
+ end
+
+ Rails.logger.info(
+ "Payload for submitted #{form_name}: Body size of #{number_to_human_size(submission_body.bytesize)} " \
+ "with #{attachment_count} attachment(s)"
+ )
+ end
+
+ def submission_body(formatted_form)
+ content = Gyoku.xml(formatted_form)
+ submission_body = soap.build_request(:save_submit_form, message: content).body
+ log_payload_info(formatted_form, submission_body)
+
+ submission_body
+ end
+ end
+ end
+end
diff --git a/lib/va1010_forms/utils.rb b/lib/va1010_forms/utils.rb
index 1052b8a511d..57258d5c81f 100644
--- a/lib/va1010_forms/utils.rb
+++ b/lib/va1010_forms/utils.rb
@@ -36,10 +36,6 @@ def soap
)
end
- def override_parsed_form(parsed_form)
- HCA::OverridesParser.new(parsed_form).override
- end
-
private
def submission_body(formatted_form)
diff --git a/lib/va_profile/configuration.rb b/lib/va_profile/configuration.rb
index b143cb1eb4e..eac2f7eb03e 100644
--- a/lib/va_profile/configuration.rb
+++ b/lib/va_profile/configuration.rb
@@ -12,7 +12,9 @@ def self.base_request_headers
end
def connection
- @conn ||= Faraday.new(base_path, headers: base_request_headers, request: request_options) do |faraday|
+ ssl_enabled = Rails.env.production?
+ @conn ||= Faraday.new(base_path, headers: base_request_headers, request: request_options,
+ ssl: { verify: ssl_enabled }) do |faraday|
faraday.use :breakers
faraday.use Faraday::Response::RaiseError
diff --git a/lib/va_profile/contact_information/service.rb b/lib/va_profile/contact_information/service.rb
index bc93e94ef04..9c33ec09689 100644
--- a/lib/va_profile/contact_information/service.rb
+++ b/lib/va_profile/contact_information/service.rb
@@ -248,8 +248,6 @@ def get_email_personalisation(type)
end
def send_contact_change_notification(transaction_status, personalisation)
- return unless Flipper.enabled?(:contact_info_change_email, @user)
-
transaction = transaction_status.transaction
if transaction.completed_success?
@@ -270,8 +268,6 @@ def send_contact_change_notification(transaction_status, personalisation)
end
def send_email_change_notification(transaction_status)
- return unless Flipper.enabled?(:contact_info_change_email, @user)
-
transaction = transaction_status.transaction
if transaction.completed_success?
diff --git a/lib/va_profile/v2/contact_information/service.rb b/lib/va_profile/v2/contact_information/service.rb
index 0536ddb07c9..6cd7d2d914d 100644
--- a/lib/va_profile/v2/contact_information/service.rb
+++ b/lib/va_profile/v2/contact_information/service.rb
@@ -224,7 +224,6 @@ def update_model(model, attr, method_name)
contact_info = VAProfileRedis::V2::ContactInformation.for_user(@user)
model.id = contact_info.public_send(attr)&.id
verb = model.id.present? ? 'put' : 'post'
-
public_send("#{verb}_#{method_name}", model)
end
diff --git a/lib/vets/attributes.rb b/lib/vets/attributes.rb
index 5cc4f76703e..ff085ef736d 100644
--- a/lib/vets/attributes.rb
+++ b/lib/vets/attributes.rb
@@ -16,8 +16,9 @@ def attributes
def attribute(name, klass, **options)
default = options[:default]
array = options[:array] || false
+ filterable = options[:filterable] || false
- attributes[name] = { type: klass, default:, array: }
+ attributes[name] = { type: klass, default:, array:, filterable: }
define_getter(name, default)
define_setter(name, klass, array)
@@ -28,6 +29,21 @@ def attribute_set
ancestors.select { |klass| klass.respond_to?(:attributes) }.flat_map { |klass| klass.attributes.keys }.uniq
end
+ # Lists the attributes that are filterable
+ def filterable_attributes
+ attributes.select { |_, options| options[:filterable] }.keys
+ end
+
+ # Creates a param hash for filterable
+ def filterable_params
+ attributes.each_with_object({}) do |attribute, hash|
+ name = attribute.first
+ options = attribute.second
+
+ hash[name.to_s] = options[:filterable] if options[:filterable]
+ end.with_indifferent_access
+ end
+
private
def define_getter(name, default)
diff --git a/lib/vets/attributes/value.rb b/lib/vets/attributes/value.rb
index a1a0fb55924..d224b7480e7 100644
--- a/lib/vets/attributes/value.rb
+++ b/lib/vets/attributes/value.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'vets/types'
+
module Vets
module Attributes
class Value
@@ -14,49 +16,22 @@ def initialize(name, klass, array: false)
end
def setter_value(value)
- validate_array(value) if @array
- value = cast_boolean(value) if @klass == Bool
- value = coerce_to_class(value)
- validate_type(value)
- value
+ type.cast(value)
end
- private
-
- def validate_array(value)
- raise TypeError, "#{@name} must be an Array" unless value.is_a?(Array)
-
- value.map! do |item|
- item.is_a?(Hash) ? @klass.new(item) : item
- end
-
- unless value.all? { |item| item.is_a?(@klass) }
- raise TypeError, "All elements of #{@name} must be of type #{@klass}"
- end
- end
-
- def cast_boolean(value)
- ActiveModel::Type::Boolean.new.cast(value)
- end
-
- def coerce_to_class(value)
- return value if value.is_a?(@klass) || value.nil?
-
- if @klass == DateTime
- begin
- value = DateTime.parse(value) if value.is_a?(String)
- rescue ArgumentError
- raise TypeError, "#{@name} could not be parsed into a DateTime"
- end
- end
-
- value.is_a?(Hash) ? @klass.new(value) : value
- end
-
- def validate_type(value)
- return if (@array && value.is_a?(Array)) || value.is_a?(@klass) || value.nil?
-
- raise TypeError, "#{@name} must be a #{@klass}"
+ # Acts as a "type factory"
+ def type
+ @type ||= if @array
+ Vets::Type::Array.new(@name, @klass)
+ elsif Vets::Type::Primitive::PRIMITIVE_TYPES.include?(@klass.name)
+ Vets::Type::Primitive.new(@name, @klass)
+ elsif @klass.module_parents.include?(Vets::Type)
+ @klass.new(@name, @klass)
+ elsif @klass == ::Hash
+ Vets::Type::Hash.new(@name)
+ else
+ Vets::Type::Object.new(@name, @klass)
+ end
end
end
end
diff --git a/lib/vets/model.rb b/lib/vets/model.rb
index 7f3986d5656..470b85a4d1a 100644
--- a/lib/vets/model.rb
+++ b/lib/vets/model.rb
@@ -1,18 +1,25 @@
# frozen_string_literal: true
require 'vets/attributes'
+require 'vets/model/dirty'
+require 'vets/model/sortable'
+require 'vets/model/pagination'
# This will be moved after virtus is removed
module Bool; end
class TrueClass; include Bool; end
class FalseClass; include Bool; end
+# This will be a replacement for Common::Base
module Vets
module Model
extend ActiveSupport::Concern
include ActiveModel::Model
include ActiveModel::Serializers::JSON
include Vets::Attributes
+ include Vets::Model::Dirty
+ include Vets::Model::Sortable
+ include Vets::Model::Pagination
included do
extend ActiveModel::Naming
diff --git a/lib/vets/model/dirty.rb b/lib/vets/model/dirty.rb
new file mode 100644
index 00000000000..bd2703c1e41
--- /dev/null
+++ b/lib/vets/model/dirty.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+# Intended to only be used with Vets::Model
+# inspired by ActiveModel::Dirty
+
+module Vets
+ module Model
+ module Dirty
+ extend ActiveSupport::Concern
+
+ included do
+ attr_reader :original_attributes
+ end
+
+ def initialize(*, **)
+ super(*, **) if defined?(super)
+ @original_attributes = attribute_values.dup
+ end
+
+ def changed?
+ changes.any?
+ end
+
+ def changed
+ changes.keys
+ end
+
+ def changes
+ attribute_values.each_with_object({}) do |(key, current_value), result|
+ original_value = @original_attributes[key]
+ result[key] = [original_value, current_value] if original_value != current_value
+ end
+ end
+ end
+ end
+end
diff --git a/lib/vets/model/pagination.rb b/lib/vets/model/pagination.rb
new file mode 100644
index 00000000000..5aa3eba316b
--- /dev/null
+++ b/lib/vets/model/pagination.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+#
+# Pagination allows Vets::Model models to set pagination info
+# for that model class.
+#
+# class User
+# include Vets::Model
+#
+# attr_accessor :name, :age
+#
+# set_pagination per_page: 21, max_per_page: 41
+#
+# ...
+# end
+#
+# User.per_page
+# => 21
+#
+# User.max_per_page
+# => 41
+#
+
+module Vets
+ module Model
+ module Pagination
+ extend ActiveSupport::Concern
+
+ DEFAULT_PER_PAGE = 10
+ DEFAULT_MAX_PER_PAGE = 100
+
+ class_methods do
+ # rubocop:disable ThreadSafety/ClassInstanceVariable
+ def set_pagination(per_page:, max_per_page:)
+ @per_page = per_page
+ @max_per_page = max_per_page
+ end
+ private :set_pagination
+
+ # Provide default values if set_pagination has not been called
+ def per_page
+ @per_page || DEFAULT_PER_PAGE
+ end
+
+ def max_per_page
+ @max_per_page || DEFAULT_MAX_PER_PAGE
+ end
+ # rubocop:enable ThreadSafety/ClassInstanceVariable
+ end
+ end
+ end
+end
diff --git a/lib/vets/model/sortable.rb b/lib/vets/model/sortable.rb
new file mode 100644
index 00000000000..9ac5130e837
--- /dev/null
+++ b/lib/vets/model/sortable.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+#
+# Sortable allows Vets::Model models to specify a default sort attribute and direction
+# for use with `#sort`.
+#
+# class User
+# include Vets::Model
+#
+# attr_accessor :name, :age
+#
+# default_sort_by name: :asc
+#
+# ...
+# end
+#
+# [user1, user3, user4, user2].sort
+#=> [user1, user2, user3, user4]
+#
+
+module Vets
+ module Model
+ module Sortable
+ include Comparable
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # sets the default sorting criteria
+ # required for use with Array#sort
+ # rubocop:disable ThreadSafety/ClassInstanceVariable
+ def default_sort_by(sort_criteria)
+ if sort_criteria.size != 1
+ raise ArgumentError, 'Only one attribute and direction can be provided in default_sort_by'
+ end
+
+ _, direction = sort_criteria.first
+ raise ArgumentError, 'Direction must be either :asc or :desc' unless %i[asc desc].include?(direction)
+
+ @default_sort_criteria = sort_criteria
+ end
+
+ def default_sort_criteria
+ @default_sort_criteria ||= {}
+ end
+ # rubocop:enable ThreadSafety/ClassInstanceVariable
+ end
+
+ def <=>(other)
+ return 0 unless self.class.default_sort_criteria.any?
+
+ attribute = self.class.default_sort_criteria.keys.first
+ direction = self.class.default_sort_criteria[attribute] || :asc
+
+ # Validate if the attribute value is comparable
+ raise ArgumentError, "Attribute '#{attribute}' is not comparable." unless comparable?(attribute)
+
+ comparison_result = public_send(attribute) <=> other.public_send(attribute)
+ direction == :desc ? -comparison_result : comparison_result
+ end
+
+ private
+
+ def comparable?(attribute)
+ value = public_send(attribute)
+ value.is_a?(Comparable)
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/array.rb b/lib/vets/type/array.rb
new file mode 100644
index 00000000000..fb9c8cf5f57
--- /dev/null
+++ b/lib/vets/type/array.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'vets/types'
+
+module Vets
+ module Type
+ class Array < Base
+ def self.primitive
+ ::Array
+ end
+
+ def cast(value)
+ return nil if value.nil?
+
+ raise TypeError, "#{@name} must be an Array" unless value.is_a?(::Array)
+
+ casted_value = value.map { |item| type.cast(item) }
+
+ unless casted_value.all? { |item| item.is_a?(@klass.try(:primitive) || @klass) }
+ raise TypeError, "All elements of #{@name} must be of type #{@klass}"
+ end
+
+ casted_value
+ end
+
+ def type
+ @type ||= if Vets::Type::Primitive::PRIMITIVE_TYPES.include?(@klass.name)
+ Vets::Type::Primitive.new(@name, @klass)
+ elsif @klass.module_parents.include?(Vets::Type)
+ @klass.new(@name, @klass)
+ elsif @klass == ::Hash
+ Vets::Type::Hash.new(@name)
+ else
+ Vets::Type::Object.new(@name, @klass)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/base.rb b/lib/vets/type/base.rb
new file mode 100644
index 00000000000..8b296618b99
--- /dev/null
+++ b/lib/vets/type/base.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+# Dir['lib/vets/type/**/*.rb'].each { |file| require file.gsub('lib/', '') }
+
+module Vets
+ module Type
+ class Base
+ def initialize(name, klass)
+ @name = name
+ @klass = klass
+ end
+
+ def cast(value)
+ raise NotImplementedError, "#{self.class} must implement #cast"
+ end
+
+ def self.primitive
+ raise NotImplementedError, "#{self.class} must implement #primitive"
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/date_time_string.rb b/lib/vets/type/date_time_string.rb
new file mode 100644
index 00000000000..991bb5822be
--- /dev/null
+++ b/lib/vets/type/date_time_string.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'vets/type/base'
+
+module Vets
+ module Type
+ class DateTimeString < Base
+ def self.primitive
+ ::String
+ end
+
+ def cast(value)
+ return nil if value.nil?
+
+ Time.parse(value).iso8601
+ rescue ArgumentError
+ raise TypeError, "#{@name} is not Time parseable"
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/hash.rb b/lib/vets/type/hash.rb
new file mode 100644
index 00000000000..d16b53d1e00
--- /dev/null
+++ b/lib/vets/type/hash.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'vets/type/base'
+
+module Vets
+ module Type
+ class Hash < Base
+ def initialize(name)
+ super(name, ::Hash)
+ end
+
+ def self.primitive
+ ::Hash
+ end
+
+ def cast(value)
+ return nil if value.nil?
+
+ raise TypeError, "#{@name} must be a Hash" unless value.is_a?(::Hash)
+
+ value
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/http_date.rb b/lib/vets/type/http_date.rb
new file mode 100644
index 00000000000..653bad5d1ed
--- /dev/null
+++ b/lib/vets/type/http_date.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'vets/type/base'
+
+module Vets
+ module Type
+ class HTTPDate < Base
+ def self.primitive
+ ::String
+ end
+
+ def cast(value)
+ return nil if value.nil?
+
+ Time.parse(value.to_s).utc.httpdate
+ rescue ArgumentError
+ raise TypeError, "#{@name} is not Time parseable"
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/iso8601_time.rb b/lib/vets/type/iso8601_time.rb
new file mode 100644
index 00000000000..888bdeb0140
--- /dev/null
+++ b/lib/vets/type/iso8601_time.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'vets/type/base'
+
+module Vets
+ module Type
+ class ISO8601Time < Base
+ def self.primitive
+ ::String
+ end
+
+ def cast(value)
+ return nil if value.nil?
+
+ Time.iso8601(value)
+ rescue ArgumentError
+ raise TypeError, "#{@name} is not iso8601"
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/object.rb b/lib/vets/type/object.rb
new file mode 100644
index 00000000000..d2fcf96e87c
--- /dev/null
+++ b/lib/vets/type/object.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'vets/type/base'
+
+module Vets
+ module Type
+ class Object < Base
+ def cast(value)
+ return nil if value.nil?
+
+ if value.is_a?(::Hash)
+ @klass.new(value)
+ elsif value.is_a?(@klass)
+ value
+ else
+ raise TypeError, "#{@name} must be a Hash or an instance of #{@klass}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/primitive.rb b/lib/vets/type/primitive.rb
new file mode 100644
index 00000000000..13009ad10d4
--- /dev/null
+++ b/lib/vets/type/primitive.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'vets/type/base'
+require 'vets/model' # this is required for Bools
+
+module Vets
+ module Type
+ class Primitive < Base
+ PRIMITIVE_TYPES = %w[String Integer Float Date Time DateTime Bool].freeze
+
+ def cast(value)
+ return value if value.is_a?(@klass) || value.nil?
+
+ begin
+ case @klass.name
+ when 'String' then ActiveModel::Type::String.new.cast(value)
+ when 'Integer' then ActiveModel::Type::Integer.new.cast(value)
+ when 'Float' then ActiveModel::Type::Float.new.cast(value)
+ when 'Date' then ActiveModel::Type::Date.new.cast(value)
+ when 'Time' then Time.zone.parse(value.to_s)
+ when 'DateTime' then ActiveModel::Type::DateTime.new.cast(value)
+ when 'Bool' then ActiveModel::Type::Boolean.new.cast(value)
+ else invalid_type!
+ end
+ rescue
+ invalid_type!
+ end
+ end
+
+ private
+
+ def invalid_type!
+ raise TypeError, "#{@name} could not be casted to #{@klass}"
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/titlecase_string.rb b/lib/vets/type/titlecase_string.rb
new file mode 100644
index 00000000000..18d030694af
--- /dev/null
+++ b/lib/vets/type/titlecase_string.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'vets/type/base'
+
+module Vets
+ module Type
+ class TitlecaseString < Base
+ def self.primitive
+ ::String
+ end
+
+ def cast(value)
+ return nil if value.nil?
+
+ value.to_s.downcase.titlecase
+ end
+ end
+ end
+end
diff --git a/lib/vets/type/utc_time.rb b/lib/vets/type/utc_time.rb
new file mode 100644
index 00000000000..6fe2059865c
--- /dev/null
+++ b/lib/vets/type/utc_time.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'vets/type/base'
+
+module Vets
+ module Type
+ class UTCTime < Base
+ def self.primitive
+ ::Time
+ end
+
+ def cast(value)
+ return nil if value.nil?
+
+ Time.parse(value.to_s).utc
+ rescue ArgumentError
+ raise TypeError, "#{@name} is not Time parseable"
+ end
+ end
+ end
+end
diff --git a/lib/vets/types.rb b/lib/vets/types.rb
new file mode 100644
index 00000000000..5feafae0948
--- /dev/null
+++ b/lib/vets/types.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# These are the castable type and
+# types that can be used by Vets::Attributes
+# Primitive types include:
+# String, Integer, Float, Date, Time, DateTime, Bool
+
+require 'vets/type/base'
+require 'vets/type/date_time_string'
+require 'vets/type/hash'
+require 'vets/type/http_date'
+require 'vets/type/iso8601_time'
+require 'vets/type/object'
+require 'vets/type/primitive'
+require 'vets/type/titlecase_string'
+require 'vets/type/utc_time'
+require 'vets/type/array'
diff --git a/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/v0/power_of_attorney_requests_controller.rb b/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/v0/power_of_attorney_requests_controller.rb
index 867b3db8d32..a7291752958 100644
--- a/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/v0/power_of_attorney_requests_controller.rb
+++ b/modules/accredited_representative_portal/app/controllers/accredited_representative_portal/v0/power_of_attorney_requests_controller.rb
@@ -3,53 +3,19 @@
module AccreditedRepresentativePortal
module V0
class PowerOfAttorneyRequestsController < ApplicationController
- POA_REQUEST_ITEM_MOCK_DATA = {
- status: 'Pending',
- declinedReason: nil,
- powerOfAttorneyCode: '091',
- submittedAt: '2024-04-30T11:03:17Z',
- acceptedOrDeclinedAt: nil,
- isAddressChangingAuthorized: false,
- isTreatmentDisclosureAuthorized: true,
- veteran: {
- firstName: 'Jon',
- middleName: nil,
- lastName: 'Smith',
- participantId: '6666666666666'
- },
- representative: {
- email: 'j2@example.com',
- firstName: 'Jane',
- lastName: 'Doe'
- },
- claimant: {
- firstName: 'Sam',
- lastName: 'Smith',
- participantId: '777777777777777',
- relationshipToVeteran: 'Child'
- },
- claimantAddress: {
- city: 'Hartford',
- state: 'CT',
- zip: '06107',
- country: 'GU',
- militaryPostOffice: nil,
- militaryPostalCode: nil
- }
- }.freeze
-
- POA_REQUEST_LIST_MOCK_DATA = [
- POA_REQUEST_ITEM_MOCK_DATA,
- POA_REQUEST_ITEM_MOCK_DATA,
- POA_REQUEST_ITEM_MOCK_DATA
- ].freeze
-
def index
- render json: POA_REQUEST_LIST_MOCK_DATA
+ poa_requests = PowerOfAttorneyRequest.includes(resolution: :resolving).limit(100)
+ serializer = PowerOfAttorneyRequestSerializer.new(poa_requests)
+
+ render json: serializer.serializable_hash, status: :ok
end
def show
- render json: POA_REQUEST_ITEM_MOCK_DATA
+ poa_request = PowerOfAttorneyRequest.includes(resolution: :resolving).find(params[:id])
+ serializer = PowerOfAttorneyRequestSerializer.new(poa_request)
+ render json: serializer.serializable_hash, status: :ok
+ rescue ActiveRecord::RecordNotFound
+ render json: { error: 'Record not found' }, status: :not_found
end
end
end
diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb
index b9d70b73e25..1f66334e366 100644
--- a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb
+++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb
@@ -2,6 +2,8 @@
module AccreditedRepresentativePortal
class PowerOfAttorneyForm < ApplicationRecord
+ self.ignored_columns += %w[city_bidx state_bidx zipcode_bidx]
+
belongs_to :power_of_attorney_request,
class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyRequest',
inverse_of: :power_of_attorney_form
@@ -13,5 +15,283 @@ class PowerOfAttorneyForm < ApplicationRecord
blind_index :city
blind_index :state
blind_index :zipcode
+
+ ##
+ # TODO: Can couple this to the schema involved in user input during POA
+ # request creation.
+ #
+ # Currently, it is a small-ish transformation of the most closely related
+ # schema in existence at the time of writing:
+ # [The schema for 2122 PDF generation](https://github.com/department-of-veterans-affairs/vets-api/blob/124adcfbeb4cba0d17f69e392d2af6189acd4809/modules/representation_management/app/swagger/v0/swagger.json#L749-L948)
+ #
+ # Of note:
+ # - Optional `dependent` property for the non-Veteran claimant (NVC) case
+ # - Does NVC necessarily mean a claimant that is a dependent of the Veteran?
+ # - If so, using the name `dependent` lets us use the word `claimant` more straightforwardly
+ # - `poa_request.claimant_type` is `{ veteran | dependent }`
+ # - All properties required but some nullable
+ # - Rather than representing optionality by omission from `required` properties
+ #
+ SCHEMA = <<~JSON
+ {
+ "type": "object",
+ "properties": {
+ "authorizations": {
+ "type": "object",
+ "properties": {
+ "record_disclosure": {
+ "type": "boolean",
+ "example": true
+ },
+ "record_disclosure_limitations": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "ALCOHOLISM",
+ "DRUG_ABUSE",
+ "HIV",
+ "SICKLE_CELL"
+ ]
+ },
+ "example": [
+ "ALCOHOLISM",
+ "DRUG_ABUSE",
+ "HIV",
+ "SICKLE_CELL"
+ ]
+ },
+ "address_change": {
+ "type": "boolean",
+ "example": false
+ }
+ },
+ "required": [
+ "record_disclosure",
+ "record_disclosure_limitations",
+ "address_change"
+ ]
+ },
+ "dependent": {
+ "type": ["object", "null"],
+ "properties": {
+ "name": {
+ "type": "object",
+ "properties": {
+ "first": {
+ "type": "string",
+ "example": "John"
+ },
+ "middle": {
+ "type": ["string", "null"],
+ "example": "Middle"
+ },
+ "last": {
+ "type": "string",
+ "example": "Doe"
+ }
+ },
+ "required": [
+ "first",
+ "middle",
+ "last"
+ ]
+ },
+ "address": {
+ "type": "object",
+ "properties": {
+ "address_line1": {
+ "type": "string",
+ "example": "123 Main St"
+ },
+ "address_line2": {
+ "type": ["string", "null"],
+ "example": "Apt 1"
+ },
+ "city": {
+ "type": "string",
+ "example": "Springfield"
+ },
+ "state_code": {
+ "type": "string",
+ "example": "IL"
+ },
+ "country": {
+ "type": "string",
+ "example": "US"
+ },
+ "zip_code": {
+ "type": "string",
+ "example": "62704"
+ },
+ "zip_code_suffix": {
+ "type": ["string", "null"],
+ "example": "6789"
+ }
+ },
+ "required": [
+ "address_line1",
+ "address_line2",
+ "city",
+ "state_code",
+ "country",
+ "zip_code",
+ "zip_code_suffix"
+ ]
+ },
+ "date_of_birth": {
+ "type": "string",
+ "format": "date",
+ "example": "1980-12-31"
+ },
+ "relationship": {
+ "type": "string",
+ "example": "Spouse"
+ },
+ "phone": {
+ "type": ["string", "null"],
+ "example": "1234567890"
+ },
+ "email": {
+ "type": ["string", "null"],
+ "example": "dependent@example.com"
+ }
+ },
+ "required": [
+ "name",
+ "address",
+ "date_of_birth",
+ "relationship",
+ "phone",
+ "email"
+ ]
+ },
+ "veteran": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "object",
+ "properties": {
+ "first": {
+ "type": "string",
+ "example": "john"
+ },
+ "middle": {
+ "type": ["string", "null"],
+ "example": "middle"
+ },
+ "last": {
+ "type": "string",
+ "example": "doe"
+ }
+ },
+ "required": [
+ "first",
+ "middle",
+ "last"
+ ]
+ },
+ "address": {
+ "type": "object",
+ "properties": {
+ "address_line1": {
+ "type": "string",
+ "example": "123 Main St"
+ },
+ "address_line2": {
+ "type": ["string", "null"],
+ "example": "Apt 1"
+ },
+ "city": {
+ "type": "string",
+ "example": "Springfield"
+ },
+ "state_code": {
+ "type": "string",
+ "example": "IL"
+ },
+ "country": {
+ "type": "string",
+ "example": "US"
+ },
+ "zip_code": {
+ "type": "string",
+ "example": "62704"
+ },
+ "zip_code_suffix": {
+ "type": ["string", "null"],
+ "example": "6789"
+ }
+ },
+ "required": [
+ "address_line1",
+ "address_line2",
+ "city",
+ "state_code",
+ "country",
+ "zip_code",
+ "zip_code_suffix"
+ ]
+ },
+ "ssn": {
+ "type": "string",
+ "example": "123456789"
+ },
+ "va_file_number": {
+ "type": ["string", "null"],
+ "example": "123456789"
+ },
+ "date_of_birth": {
+ "type": "string",
+ "format": "date",
+ "example": "1980-12-31"
+ },
+ "service_number": {
+ "type": ["string", "null"],
+ "example": "123456789"
+ },
+ "service_branch": {
+ "type": ["string", "null"],
+ "enum": [
+ "ARMY",
+ "NAVY",
+ "AIR_FORCE",
+ "MARINE_CORPS",
+ "COAST_GUARD",
+ "SPACE_FORCE",
+ "NOAA",
+ "USPHS"
+ ],
+ "example": "ARMY"
+ },
+ "phone": {
+ "type": ["string", "null"],
+ "example": "1234567890"
+ },
+ "email": {
+ "type": ["string", "null"],
+ "example": "veteran@example.com"
+ }
+ },
+ "required": [
+ "name",
+ "address",
+ "ssn",
+ "va_file_number",
+ "date_of_birth",
+ "service_number",
+ "service_branch",
+ "phone",
+ "email"
+ ]
+ }
+ },
+ "required": [
+ "authorizations",
+ "veteran",
+ "dependent"
+ ]
+ }
+ JSON
end
end
diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb
index 6dcc0a03e41..3bcd9768516 100644
--- a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb
+++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb
@@ -2,10 +2,13 @@
module AccreditedRepresentativePortal
class PowerOfAttorneyRequestDecision < ApplicationRecord
- include PowerOfAttorneyRequestResolution::Resolving
-
self.inheritance_column = nil
+ module Types
+ ACCEPTANCE = 'AccreditedRepresentativePortal::PowerOfAttorneyRequestAcceptance'
+ DECLINATION = 'AccreditedRepresentativePortal::PowerOfAttorneyRequestDeclination'
+ end
+
belongs_to :creator,
class_name: 'UserAccount'
end
diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb
index a2a6fd4cd9e..5098794bf72 100644
--- a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb
+++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb
@@ -2,6 +2,5 @@
module AccreditedRepresentativePortal
class PowerOfAttorneyRequestExpiration < ApplicationRecord
- include PowerOfAttorneyRequestResolution::Resolving
end
end
diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb
index 96754cbb86d..05c0f329514 100644
--- a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb
+++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb
@@ -16,13 +16,5 @@ class PowerOfAttorneyRequestResolution < ApplicationRecord
has_kms_key
has_encrypted :reason, key: :kms_key, **lockbox_options
-
- module Resolving
- extend ActiveSupport::Concern
-
- included do
- has_one :power_of_attorney_request_resolution, as: :resolving
- end
- end
end
end
diff --git a/modules/accredited_representative_portal/app/policies/accredited_representative_portal/application_policy.rb b/modules/accredited_representative_portal/app/policies/accredited_representative_portal/application_policy.rb
index e8039214183..c5f3d0b6159 100644
--- a/modules/accredited_representative_portal/app/policies/accredited_representative_portal/application_policy.rb
+++ b/modules/accredited_representative_portal/app/policies/accredited_representative_portal/application_policy.rb
@@ -46,7 +46,7 @@ def destroy?
def override_warning
Rails.logger.warn(
- "#{self.class} is using the default ##{caller_locations(1, 1)[0].label} implementation. \
+ "#{self.class} is using the default ##{caller_locations(1, 1)[0].base_label} implementation. \
Consider overriding it."
)
end
diff --git a/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_decision_serializer.rb b/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_decision_serializer.rb
new file mode 100644
index 00000000000..4a1350887c8
--- /dev/null
+++ b/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_decision_serializer.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module AccreditedRepresentativePortal
+ class PowerOfAttorneyRequestDecisionSerializer < PowerOfAttorneyRequestResolutionSerializer
+ attribute :decision_type do |resolution|
+ case resolution.resolving.type
+ when PowerOfAttorneyRequestDecision::Types::ACCEPTANCE
+ 'acceptance'
+ when PowerOfAttorneyRequestDecision::Types::DECLINATION
+ 'declination'
+ end
+ end
+
+ attribute :reason, if: proc { |resolution|
+ resolution.resolving.type == PowerOfAttorneyRequestDecision::Types::DECLINATION
+ }
+
+ attribute :creator_id do |resolution|
+ resolution.resolving.creator_id
+ end
+ end
+end
diff --git a/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_expiration_serializer.rb b/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_expiration_serializer.rb
new file mode 100644
index 00000000000..239c1457c8d
--- /dev/null
+++ b/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_expiration_serializer.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+module AccreditedRepresentativePortal
+ class PowerOfAttorneyRequestExpirationSerializer < PowerOfAttorneyRequestResolutionSerializer
+ end
+end
diff --git a/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_resolution_serializer.rb b/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_resolution_serializer.rb
new file mode 100644
index 00000000000..5aa195f560b
--- /dev/null
+++ b/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_resolution_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module AccreditedRepresentativePortal
+ class PowerOfAttorneyRequestResolutionSerializer
+ include JSONAPI::Serializer
+
+ attributes :created_at
+ end
+end
diff --git a/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_serializer.rb b/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_serializer.rb
new file mode 100644
index 00000000000..242bbc1db1a
--- /dev/null
+++ b/modules/accredited_representative_portal/app/serializers/accredited_representative_portal/power_of_attorney_request_serializer.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module AccreditedRepresentativePortal
+ class PowerOfAttorneyRequestSerializer
+ include JSONAPI::Serializer
+
+ attributes :claimant_id, :created_at
+
+ attribute :resolution do |poa_request|
+ next unless poa_request.resolution
+
+ serializer =
+ case poa_request.resolution.resolving
+ when PowerOfAttorneyRequestDecision
+ PowerOfAttorneyRequestDecisionSerializer
+ when PowerOfAttorneyRequestExpiration
+ PowerOfAttorneyRequestExpirationSerializer
+ end
+
+ serializer.new(poa_request.resolution)
+ end
+ end
+end
diff --git a/modules/accredited_representative_portal/bin/rails b/modules/accredited_representative_portal/bin/rails
index a73663192c2..dabc19f897e 100755
--- a/modules/accredited_representative_portal/bin/rails
+++ b/modules/accredited_representative_portal/bin/rails
@@ -1,11 +1,14 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
-ENGINE_ROOT = File.expand_path('../..', __dir__)
-ENGINE_PATH = File.expand_path('../../lib/accredited_representative_portal/engine', __dir__)
+require 'pathname'
+
+ENGINE_ROOT = Pathname(__dir__).parent
+ENGINE_PATH = ENGINE_ROOT / 'lib/accredited_representative_portal/engine'
+BUNDLE_GEMFILE = ENGINE_ROOT / 'Gemfile'
# Set up gems listed in the Gemfile.
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __dir__)
+ENV['BUNDLE_GEMFILE'] ||= BUNDLE_GEMFILE.to_path
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
require 'rails/all'
diff --git a/modules/accredited_representative_portal/db/migrate/20241227212839_remove_old_indices_from_poa_forms.rb b/modules/accredited_representative_portal/db/migrate/20241227212839_remove_old_indices_from_poa_forms.rb
new file mode 100644
index 00000000000..e0d142fcf36
--- /dev/null
+++ b/modules/accredited_representative_portal/db/migrate/20241227212839_remove_old_indices_from_poa_forms.rb
@@ -0,0 +1,6 @@
+class RemoveOldIndicesFromPoaForms < ActiveRecord::Migration[7.1]
+ def change
+ remove_index :ar_power_of_attorney_forms, name: 'idx_on_city_bidx_state_bidx_zipcode_bidx_a85b76f9bc', column: [:city_bidx, :state_bidx, :zipcode_bidx]
+ remove_index :ar_power_of_attorney_forms, name: 'index_ar_power_of_attorney_forms_on_zipcode_bidx', column: :zipcode_bidx
+ end
+end
diff --git a/modules/accredited_representative_portal/db/migrate/20241227212922_remove_columns_from_poa_forms.rb b/modules/accredited_representative_portal/db/migrate/20241227212922_remove_columns_from_poa_forms.rb
new file mode 100644
index 00000000000..980aa3c57db
--- /dev/null
+++ b/modules/accredited_representative_portal/db/migrate/20241227212922_remove_columns_from_poa_forms.rb
@@ -0,0 +1,5 @@
+class RemoveColumnsFromPoaForms < ActiveRecord::Migration[7.1]
+ def change
+ safety_assured { remove_columns :ar_power_of_attorney_forms, :city_bidx, :state_bidx, :zipcode_bidx, type: :string }
+ end
+end
diff --git a/modules/accredited_representative_portal/db/migrate/20241227212942_add_columns_to_poa_forms.rb b/modules/accredited_representative_portal/db/migrate/20241227212942_add_columns_to_poa_forms.rb
new file mode 100644
index 00000000000..eb6ccded31c
--- /dev/null
+++ b/modules/accredited_representative_portal/db/migrate/20241227212942_add_columns_to_poa_forms.rb
@@ -0,0 +1,12 @@
+class AddColumnsToPoaForms < ActiveRecord::Migration[7.1]
+ def change
+ add_column :ar_power_of_attorney_forms, :claimant_city_ciphertext, :string, null: false
+ add_column :ar_power_of_attorney_forms, :claimant_city_bidx, :string, null: false
+
+ add_column :ar_power_of_attorney_forms, :claimant_state_code_ciphertext, :string, null: false
+ add_column :ar_power_of_attorney_forms, :claimant_state_code_bidx, :string, null: false
+
+ add_column :ar_power_of_attorney_forms, :claimant_zip_code_ciphertext, :string, null: false
+ add_column :ar_power_of_attorney_forms, :claimant_zip_code_bidx, :string, null: false
+ end
+end
diff --git a/modules/accredited_representative_portal/db/migrate/20241227213018_add_new_index_to_poa_forms.rb b/modules/accredited_representative_portal/db/migrate/20241227213018_add_new_index_to_poa_forms.rb
new file mode 100644
index 00000000000..27421549c74
--- /dev/null
+++ b/modules/accredited_representative_portal/db/migrate/20241227213018_add_new_index_to_poa_forms.rb
@@ -0,0 +1,9 @@
+class AddNewIndexToPoaForms < ActiveRecord::Migration[7.1]
+ disable_ddl_transaction!
+
+ def change
+ add_index :ar_power_of_attorney_forms,
+ [:claimant_city_bidx, :claimant_state_code_bidx, :claimant_zip_code_bidx],
+ algorithm: :concurrently
+ end
+end
diff --git a/modules/accredited_representative_portal/db/migrate/20241227213059_add_claimant_type_to_poa_requests.rb b/modules/accredited_representative_portal/db/migrate/20241227213059_add_claimant_type_to_poa_requests.rb
new file mode 100644
index 00000000000..b8f1ae170bf
--- /dev/null
+++ b/modules/accredited_representative_portal/db/migrate/20241227213059_add_claimant_type_to_poa_requests.rb
@@ -0,0 +1,5 @@
+class AddClaimantTypeToPoaRequests < ActiveRecord::Migration[7.1]
+ def change
+ add_column :ar_power_of_attorney_requests, :claimant_type, :string, null: false
+ end
+end
diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_decision.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_decision.rb
deleted file mode 100644
index fd6f2e65d66..00000000000
--- a/modules/accredited_representative_portal/spec/factories/power_of_attorney_decision.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :power_of_attorney_request_decision,
- class: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision' do
- id { Faker::Internet.uuid }
- association :creator, factory: :user_account
- type { 'Approval' }
- end
-end
diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_form.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_form.rb
index 779efce161d..4542c038c09 100644
--- a/modules/accredited_representative_portal/spec/factories/power_of_attorney_form.rb
+++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_form.rb
@@ -2,11 +2,9 @@
FactoryBot.define do
factory :power_of_attorney_form, class: 'AccreditedRepresentativePortal::PowerOfAttorneyForm' do
- association :power_of_attorney_request, factory: :power_of_attorney_request
data_ciphertext { 'Test encrypted data' }
city_bidx { Faker::Alphanumeric.alphanumeric(number: 44) }
state_bidx { Faker::Alphanumeric.alphanumeric(number: 44) }
zipcode_bidx { Faker::Alphanumeric.alphanumeric(number: 44) }
- encrypted_kms_key { SecureRandom.hex(16) }
end
end
diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_request.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request.rb
index 0aa23eea4f6..866f3dc4c9d 100644
--- a/modules/accredited_representative_portal/spec/factories/power_of_attorney_request.rb
+++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request.rb
@@ -3,7 +3,17 @@
FactoryBot.define do
factory :power_of_attorney_request, class: 'AccreditedRepresentativePortal::PowerOfAttorneyRequest' do
association :claimant, factory: :user_account
- id { Faker::Internet.uuid }
- created_at { Time.current }
+
+ trait :with_acceptance do
+ resolution { create(:power_of_attorney_request_resolution, :acceptance) }
+ end
+
+ trait :with_declination do
+ resolution { create(:power_of_attorney_request_resolution, :declination) }
+ end
+
+ trait :with_expiration do
+ resolution { create(:power_of_attorney_request_resolution, :expiration) }
+ end
end
end
diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_decision.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_decision.rb
new file mode 100644
index 00000000000..6e249a2edda
--- /dev/null
+++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_decision.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :power_of_attorney_request_decision,
+ class: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision' do
+ association :creator, factory: :user_account
+
+ trait :acceptance do
+ type { AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision::Types::ACCEPTANCE }
+ end
+
+ trait :declination do
+ type { AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision::Types::DECLINATION }
+ end
+ end
+end
diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_expiration.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_expiration.rb
similarity index 73%
rename from modules/accredited_representative_portal/spec/factories/power_of_attorney_expiration.rb
rename to modules/accredited_representative_portal/spec/factories/power_of_attorney_request_expiration.rb
index 2f294a2a9fb..774154b0719 100644
--- a/modules/accredited_representative_portal/spec/factories/power_of_attorney_expiration.rb
+++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_expiration.rb
@@ -2,7 +2,5 @@
FactoryBot.define do
factory :power_of_attorney_request_expiration,
- class: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration' do
- id { Faker::Internet.uuid }
- end
+ class: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration'
end
diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb
index 20798bf1d54..ae0e72cfae8 100644
--- a/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb
+++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb
@@ -3,49 +3,19 @@
FactoryBot.define do
factory :power_of_attorney_request_resolution,
class: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestResolution' do
- association :power_of_attorney_request, factory: :power_of_attorney_request
- resolving_id { SecureRandom.uuid }
- reason_ciphertext { 'Encrypted Reason' }
- created_at { Time.current }
- encrypted_kms_key { SecureRandom.hex(16) }
+ power_of_attorney_request
- trait :with_expiration do
- resolving_type { 'AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration' }
- resolving { create(:power_of_attorney_request_expiration) }
+ trait :acceptance do
+ resolving { create(:power_of_attorney_request_decision, :acceptance) }
end
- trait :with_decision do
- resolving_type { 'AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision' }
- resolving { create(:power_of_attorney_request_decision) }
+ trait :declination do
+ resolving { create(:power_of_attorney_request_decision, :declination) }
+ reason { "Didn't authorize treatment record disclosure" }
end
- trait :with_invalid_type do
- resolving_type { 'AccreditedRepresentativePortal::InvalidType' }
- resolving { AccreditedRepresentativePortal::InvalidType.new }
+ trait :expiration do
+ resolving { create(:power_of_attorney_request_expiration) }
end
end
end
-
-module AccreditedRepresentativePortal
- class InvalidType
- def method_missing(_method, *_args) = self
-
- def respond_to_missing?(_method, _include_private = false) = true
-
- def id = nil
-
- def self.method_missing(_method, *_args) = NullObject.new
-
- def self.respond_to_missing?(_method, _include_private = false) = true
- end
-
- class NullObject
- def method_missing(_method, *_args) = self
-
- def respond_to_missing?(*) = true
-
- def nil? = true
-
- def to_s = ''
- end
-end
diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_form_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_form_spec.rb
deleted file mode 100644
index 0767cf0ceca..00000000000
--- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_form_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../rails_helper'
-
-RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyForm, type: :model do
- describe 'associations' do
- it { is_expected.to belong_to(:power_of_attorney_request) }
- end
-
- describe 'creation' do
- it 'creates a valid form' do
- form = build(:power_of_attorney_form, data_ciphertext: 'test_data')
- expect(form).to be_valid
- end
- end
-end
diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_decision_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_decision_spec.rb
deleted file mode 100644
index c2376dc7ecd..00000000000
--- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_decision_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../rails_helper'
-
-RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision, type: :model do
- describe 'associations' do
- it { is_expected.to belong_to(:creator).class_name('UserAccount') }
- it { is_expected.to have_one(:power_of_attorney_request_resolution) }
- end
-end
diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_expiration_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_expiration_spec.rb
deleted file mode 100644
index 2213312df20..00000000000
--- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_expiration_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../rails_helper'
-
-RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration, type: :model do
- describe 'associations' do
- it { is_expected.to have_one(:power_of_attorney_request_resolution) }
- end
-
- describe 'validations' do
- it 'creates a valid record' do
- expiration = create(:power_of_attorney_request_expiration)
- expect(expiration).to be_valid
- end
- end
-end
diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_resolution_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_resolution_spec.rb
deleted file mode 100644
index e7a006aac77..00000000000
--- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_resolution_spec.rb
+++ /dev/null
@@ -1,142 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../rails_helper'
-
-mod = AccreditedRepresentativePortal
-RSpec.describe mod::PowerOfAttorneyRequestResolution, type: :model do
- describe 'associations' do
- let(:power_of_attorney_request) { create(:power_of_attorney_request) }
-
- it { is_expected.to belong_to(:power_of_attorney_request) }
-
- it 'can resolve to PowerOfAttorneyRequestExpiration' do
- expiration = create(:power_of_attorney_request_expiration)
- resolution = described_class.create!(
- resolving: expiration,
- power_of_attorney_request: power_of_attorney_request,
- created_at: Time.zone.now,
- encrypted_kms_key: SecureRandom.hex(16)
- )
-
- expect(resolution.resolving).to eq(expiration)
- expect(resolution.resolving_type).to eq('AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration')
- end
-
- it 'can resolve to PowerOfAttorneyRequestDecision' do
- decision = create(:power_of_attorney_request_decision)
- resolution = described_class.create!(
- resolving: decision,
- power_of_attorney_request: power_of_attorney_request,
- created_at: Time.zone.now,
- encrypted_kms_key: SecureRandom.hex(16)
- )
-
- expect(resolution.resolving).to eq(decision)
- expect(resolution.resolving_type).to eq('AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision')
- end
- end
-
- describe 'delegated_type resolving' do
- it 'is valid with expiration resolving' do
- resolution = create(:power_of_attorney_request_resolution, :with_expiration)
- expect(resolution).to be_valid
- expect(resolution.resolving).to be_a(mod::PowerOfAttorneyRequestExpiration)
- end
-
- it 'is valid with decision resolving' do
- resolution = create(:power_of_attorney_request_resolution, :with_decision)
- expect(resolution).to be_valid
- expect(resolution.resolving).to be_a(mod::PowerOfAttorneyRequestDecision)
- end
-
- it 'is invalid with null resolving_type and resolving_id' do
- resolution = build(:power_of_attorney_request_resolution, resolving_type: nil, resolving_id: nil)
- expect(resolution).not_to be_valid
- end
- end
-
- describe 'heterogeneous list behavior' do
- it 'conveniently returns heterogeneous lists' do
- travel_to Time.zone.parse('2024-11-25T09:46:24Z') do
- creator = create(:user_account)
-
- ids = []
-
- # Persisted resolving records
- decision_acceptance = mod::PowerOfAttorneyRequestDecision.create!(
- type: 'acceptance',
- creator: creator
- )
- decision_declination = mod::PowerOfAttorneyRequestDecision.create!(
- type: 'declination',
- creator: creator
- )
- expiration = mod::PowerOfAttorneyRequestExpiration.create!
-
- # Associate resolving records
- ids << described_class.create!(
- power_of_attorney_request: create(:power_of_attorney_request),
- resolving: decision_acceptance,
- encrypted_kms_key: SecureRandom.hex(16),
- created_at: Time.current
- ).id
-
- ids << described_class.create!(
- power_of_attorney_request: create(:power_of_attorney_request),
- resolving: decision_declination,
- encrypted_kms_key: SecureRandom.hex(16),
- created_at: Time.current
- ).id
-
- ids << described_class.create!(
- power_of_attorney_request: create(:power_of_attorney_request),
- resolving: expiration,
- encrypted_kms_key: SecureRandom.hex(16),
- created_at: Time.current
- ).id
-
- resolutions = described_class.includes(:resolving).find(ids)
-
- # Serialize for comparison
- actual =
- resolutions.map do |resolution|
- serialized =
- case resolution.resolving
- when mod::PowerOfAttorneyRequestDecision
- {
- type: 'decision',
- decision_type: resolution.resolving.type
- }
- when mod::PowerOfAttorneyRequestExpiration
- {
- type: 'expiration'
- }
- end
-
- serialized.merge!(
- created_at: resolution.created_at.iso8601
- )
- end
-
- expect(actual).to eq(
- [
- {
- type: 'decision',
- decision_type: 'acceptance',
- created_at: '2024-11-25T09:46:24Z'
- },
- {
- type: 'decision',
- decision_type: 'declination',
- created_at: '2024-11-25T09:46:24Z'
- },
- {
- type: 'expiration',
- created_at: '2024-11-25T09:46:24Z'
- }
- ]
- )
- end
- end
- end
-end
diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_spec.rb
deleted file mode 100644
index 81b29d90317..00000000000
--- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../rails_helper'
-
-RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyRequest, type: :model do
- describe 'associations' do
- it { is_expected.to belong_to(:claimant).class_name('UserAccount') }
- it { is_expected.to have_one(:power_of_attorney_form) }
- it { is_expected.to have_one(:resolution) }
- end
-end
diff --git a/modules/accredited_representative_portal/spec/requests/accredited_representative_portal/v0/power_of_attorney_requests_spec.rb b/modules/accredited_representative_portal/spec/requests/accredited_representative_portal/v0/power_of_attorney_requests_spec.rb
index c1349f2db60..88e0d841866 100644
--- a/modules/accredited_representative_portal/spec/requests/accredited_representative_portal/v0/power_of_attorney_requests_spec.rb
+++ b/modules/accredited_representative_portal/spec/requests/accredited_representative_portal/v0/power_of_attorney_requests_spec.rb
@@ -4,47 +4,134 @@
RSpec.describe AccreditedRepresentativePortal::V0::PowerOfAttorneyRequestsController, type: :request do
let(:test_user) { create(:representative_user) }
- let(:poa_request_details_id) { '123' }
- let(:poa_request_details_mock_data) do
- {
- 'status' => 'Pending',
- 'declinedReason' => nil,
- 'powerOfAttorneyCode' => '091',
- 'submittedAt' => '2024-04-30T11:03:17Z',
- 'acceptedOrDeclinedAt' => nil,
- 'isAddressChangingAuthorized' => false,
- 'isTreatmentDisclosureAuthorized' => true,
- 'veteran' => { 'firstName' => 'Jon', 'middleName' => nil, 'lastName' => 'Smith',
- 'participantId' => '6666666666666' },
- 'representative' => { 'email' => 'j2@example.com', 'firstName' => 'Jane', 'lastName' => 'Doe' },
- 'claimant' => { 'firstName' => 'Sam', 'lastName' => 'Smith', 'participantId' => '777777777777777',
- 'relationshipToVeteran' => 'Child' },
- 'claimantAddress' => { 'city' => 'Hartford', 'state' => 'CT', 'zip' => '06107', 'country' => 'GU',
- 'militaryPostOffice' => nil, 'militaryPostalCode' => nil }
- }
- end
- let(:poa_request_list_mock_data) do
- [poa_request_details_mock_data, poa_request_details_mock_data, poa_request_details_mock_data]
+ let(:poa_request) { create(:power_of_attorney_request_resolution, :declination).power_of_attorney_request }
+ let(:time) { '2024-12-21T04:45:37.458Z' }
+
+ let(:poa_requests) do
+ [].tap do |memo|
+ memo << create(:power_of_attorney_request)
+ memo << create(:power_of_attorney_request_resolution, :acceptance).power_of_attorney_request
+ memo << create(:power_of_attorney_request_resolution, :declination).power_of_attorney_request
+ memo << create(:power_of_attorney_request_resolution, :expiration).power_of_attorney_request
+ end
end
before do
Flipper.enable(:accredited_representative_portal_pilot)
login_as(test_user)
+ travel_to(time)
end
describe 'GET /accredited_representative_portal/v0/power_of_attorney_requests' do
- it 'returns the list of a power of attorney request' do
+ it 'returns the list of power of attorney requests', skip: 'temp skip' do
+ poa_requests
+
get('/accredited_representative_portal/v0/power_of_attorney_requests')
+
+ parsed_response = JSON.parse(response.body)
expect(response).to have_http_status(:ok)
- expect(JSON.parse(response.body)).to eq(poa_request_list_mock_data)
+
+ expect(parsed_response).to eq(
+ 'data' => [
+ {
+ 'id' => poa_requests[0].id,
+ 'type' => 'power_of_attorney_request',
+ 'attributes' => {
+ 'claimant_id' => poa_requests[0].claimant_id,
+ 'created_at' => time,
+ 'resolution' => nil
+ }
+ },
+ {
+ 'id' => poa_requests[1].id,
+ 'type' => 'power_of_attorney_request',
+ 'attributes' => {
+ 'claimant_id' => poa_requests[1].claimant_id,
+ 'created_at' => time,
+ 'resolution' => {
+ 'data' => {
+ 'id' => poa_requests[1].resolution.id,
+ 'type' => 'power_of_attorney_request_decision',
+ 'attributes' => {
+ 'created_at' => time,
+ 'creator_id' => poa_requests[1].resolution.resolving.creator_id,
+ 'decision_type' => 'acceptance'
+ }
+ }
+ }
+ }
+ },
+ {
+ 'id' => poa_requests[2].id,
+ 'type' => 'power_of_attorney_request',
+ 'attributes' => {
+ 'claimant_id' => poa_requests[2].claimant_id,
+ 'created_at' => time,
+ 'resolution' => {
+ 'data' => {
+ 'id' => poa_requests[2].resolution.id,
+ 'type' => 'power_of_attorney_request_decision',
+ 'attributes' => {
+ 'created_at' => time,
+ 'creator_id' => poa_requests[2].resolution.resolving.creator_id,
+ 'reason' => 'Didn\'t authorize treatment record disclosure',
+ 'decision_type' => 'declination'
+ }
+ }
+ }
+ }
+ },
+ {
+ 'id' => poa_requests[3].id,
+ 'type' => 'power_of_attorney_request',
+ 'attributes' => {
+ 'claimant_id' => poa_requests[3].claimant_id,
+ 'created_at' => time,
+ 'resolution' => {
+ 'data' => {
+ 'id' => poa_requests[3].resolution.id,
+ 'type' => 'power_of_attorney_request_expiration',
+ 'attributes' => {
+ 'created_at' => time
+ }
+ }
+ }
+ }
+ }
+ ]
+ )
end
end
describe 'GET /accredited_representative_portal/v0/power_of_attorney_requests/:id' do
- it 'returns the details of a power of attorney request' do
- get("/accredited_representative_portal/v0/power_of_attorney_requests/#{poa_request_details_id}")
+ it 'returns the details of a specific power of attorney request', skip: 'temp skip' do
+ get("/accredited_representative_portal/v0/power_of_attorney_requests/#{poa_request.id}")
+
+ parsed_response = JSON.parse(response.body)
expect(response).to have_http_status(:ok)
- expect(JSON.parse(response.body)).to include(poa_request_details_mock_data)
+
+ expect(parsed_response).to eq(
+ 'data' => {
+ 'id' => poa_request.id,
+ 'type' => 'power_of_attorney_request',
+ 'attributes' => {
+ 'claimant_id' => poa_request.claimant_id,
+ 'created_at' => time,
+ 'resolution' => {
+ 'data' => {
+ 'id' => poa_request.resolution.id,
+ 'type' => 'power_of_attorney_request_decision',
+ 'attributes' => {
+ 'created_at' => time,
+ 'creator_id' => poa_request.resolution.resolving.creator_id,
+ 'reason' => 'Didn\'t authorize treatment record disclosure',
+ 'decision_type' => 'declination'
+ }
+ }
+ }
+ }
+ }
+ )
end
end
end
diff --git a/modules/appeals_api/app/services/appeals_api/remove_pii.rb b/modules/appeals_api/app/services/appeals_api/remove_pii.rb
index 08f15d1f921..5c34515c794 100644
--- a/modules/appeals_api/app/services/appeals_api/remove_pii.rb
+++ b/modules/appeals_api/app/services/appeals_api/remove_pii.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'ddtrace'
+require 'datadog'
module AppealsApi
class RemovePii
diff --git a/modules/avs/app/models/avs/v0/after_visit_summary.rb b/modules/avs/app/models/avs/v0/after_visit_summary.rb
index f7cd4534613..4292c4d4145 100644
--- a/modules/avs/app/models/avs/v0/after_visit_summary.rb
+++ b/modules/avs/app/models/avs/v0/after_visit_summary.rb
@@ -8,8 +8,8 @@ class V0::AfterVisitSummary
attribute :id, String
attribute :icn, String
- attribute :meta, Object
- attribute :patient_info, Object
+ attribute :meta, Hash
+ attribute :patient_info, Hash
attribute :appointment_iens, Array, default: []
attribute :clinics_visited, Array, default: []
attribute :providers, Array, default: []
@@ -29,14 +29,14 @@ class V0::AfterVisitSummary
attribute :problems, Array, default: []
attribute :clinical_reminders, Array, default: []
attribute :clinical_services, Array, default: []
- attribute :allergies_reactions, Object
+ attribute :allergies_reactions, Hash
attribute :clinic_medications, Array, default: []
attribute :va_medications, Array, default: []
attribute :nonva_medications, Array, default: []
- attribute :med_changes_summary, Object
+ attribute :med_changes_summary, Hash
attribute :lab_results, Array, default: []
attribute :radiology_reports1_yr, String
- attribute :discrete_data, Object
+ attribute :discrete_data, Hash
attribute :more_help_and_information, String
def initialize(data)
diff --git a/modules/burials/lib/burials/engine.rb b/modules/burials/lib/burials/engine.rb
index dbbb99afce0..5b21f2ad8f9 100644
--- a/modules/burials/lib/burials/engine.rb
+++ b/modules/burials/lib/burials/engine.rb
@@ -32,9 +32,8 @@ class Engine < ::Rails::Engine
require 'lighthouse/benefits_intake/sidekiq/submission_status_job'
require_relative '../benefits_intake/submission_handler'
- # Register our Pension Benefits Intake Submission Handler
+ # Register our Burial Benefits Intake Submission Handler
::BenefitsIntake::SubmissionStatusJob.register_handler('21P-530EZ', Burials::BenefitsIntake::SubmissionHandler)
- ::BenefitsIntake::SubmissionStatusJob.register_handler('21P-530', Burials::BenefitsIntake::SubmissionHandler)
end
end
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 d478bd2bfd2..99f34152cfc 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
@@ -144,7 +144,7 @@ module IntentToFileWebService
##
# OrgWebServiceBean
#
- module OrgWebService
+ module OrgWebServiceBean
DEFINITION =
Bean.new(
path: 'OrgWebServiceBean',
@@ -155,6 +155,14 @@ module OrgWebService
)
end
+ module OrgWebService
+ DEFINITION =
+ Service.new(
+ bean: OrgWebServiceBean::DEFINITION,
+ path: 'OrgWebService'
+ )
+ end
+
##
# PersonWebServiceBean
#
diff --git a/modules/claims_api/app/controllers/claims_api/v2/blueprints/power_of_attorney_request_blueprint.rb b/modules/claims_api/app/controllers/claims_api/v2/blueprints/power_of_attorney_request_blueprint.rb
index d729a288dfe..61d92dc0045 100644
--- a/modules/claims_api/app/controllers/claims_api/v2/blueprints/power_of_attorney_request_blueprint.rb
+++ b/modules/claims_api/app/controllers/claims_api/v2/blueprints/power_of_attorney_request_blueprint.rb
@@ -4,97 +4,62 @@ module ClaimsApi
module V2
module Blueprints
class PowerOfAttorneyRequestBlueprint < Blueprinter::Base
- class Veteran < Blueprinter::Base
- transform Transformers::LowerCamelTransformer
-
- fields(
- :first_name,
- :middle_name,
- :last_name
- )
- end
-
- class Representative < Blueprinter::Base
- transform Transformers::LowerCamelTransformer
-
- fields(
- :first_name,
- :last_name,
- :email
- )
+ view :index do
+ field :id do |request|
+ request['id']
+ end
+
+ field :type do
+ 'power-of-attorney-request'
+ end
+
+ field :attributes do |request|
+ {
+ veteran: {
+ first_name: request['vetFirstName'],
+ last_name: request['vetLastName'],
+ middle_name: request['vetMiddleName']
+ },
+ claimant: {
+ city: request['claimantCity'],
+ country: request['claimantCountry'],
+ military_po: request['claimantMilitaryPO'],
+ military_postal_code: request['claimantMilitaryPostalCode'],
+ state: request['claimantState'],
+ zip: request['claimantZip']
+ },
+ representative: {
+ poa_code: request['poaCode'],
+ vso_user_email: request['VSOUserEmail'],
+ vso_user_first_name: request['VSOUserFirstName'],
+ vso_user_last_name: request['VSOUserLastName']
+ },
+ received_date: request['dateRequestReceived'],
+ actioned_date: request['dateRequestActioned'],
+ status: request['secondaryStatus'],
+ declined_reason: request['declinedReason'],
+ change_address_authorization: request['changeAddressAuth'],
+ health_info_authorization: request['healthInfoAuth']
+ }
+ end
+
+ transform ClaimsApi::V2::Blueprints::Transformers::LowerCamelTransformer
end
- class Decision < Blueprinter::Base
- transform Transformers::LowerCamelTransformer
-
- fields(
- :status,
- :declining_reason
- )
-
- field(
- :created_at,
- datetime_format: :iso8601.to_proc
- )
-
- association :created_by, blueprint: Representative
- end
-
- class Claimant < Blueprinter::Base
- transform Transformers::LowerCamelTransformer
-
- fields(
- :first_name,
- :last_name,
- :relationship_to_veteran
- )
- end
-
- class Address < Blueprinter::Base
- transform Transformers::LowerCamelTransformer
-
- fields(
- :city, :state, :zip, :country,
- :military_post_office,
- :military_postal_code
- )
- end
-
- class Attributes < Blueprinter::Base
- transform Transformers::LowerCamelTransformer
-
- fields(
- :power_of_attorney_code
- )
-
- field(
- :authorizes_address_changing,
- name: :is_address_changing_authorized
- )
-
- field(
- :authorizes_treatment_disclosure,
- name: :is_treatment_disclosure_authorized
- )
-
- association :veteran, blueprint: Veteran
- association :claimant, blueprint: Claimant
- association :claimant_address, blueprint: Address
- association :decision, blueprint: Decision
-
- field(
- :created_at,
- datetime_format: :iso8601.to_proc
- )
- end
+ view :create do
+ field :id do |request|
+ request['id']
+ end
- transform Transformers::LowerCamelTransformer
+ field :type do
+ 'power-of-attorney-request'
+ end
- identifier :id
- field(:type) { 'powerOfAttorneyRequest' }
+ field :attributes do |request|
+ request.except('id')
+ end
- association :attributes, blueprint: Attributes do |poa_request|
- poa_request
+ transform ClaimsApi::V2::Blueprints::Transformers::LowerCamelTransformer
end
end
end
diff --git a/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb b/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb
index 2b9a942b801..a9ad6ba73f5 100644
--- a/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb
+++ b/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/base_controller.rb
@@ -111,11 +111,9 @@ def submit_power_of_attorney(poa_code, form_number)
def set_auth_headers
headers = auth_headers.merge!({ VA_NOTIFY_KEY => icn_for_vanotify })
- if allow_dependent_claimant?
- add_dependent_to_auth_headers(headers)
- else
- auth_headers
- end
+ add_dependent_to_auth_headers(headers) if allow_dependent_claimant?
+
+ headers
end
def add_dependent_to_auth_headers(headers)
@@ -228,11 +226,11 @@ def user_profile
end
def icn_for_vanotify
- params[:veteranId]
+ dependent_claimant_icn = claimant_icn
+ dependent_claimant_icn.presence || params[:veteranId]
end
def fetch_claimant
- claimant_icn = form_attributes.dig('claimant', 'claimantId')
if claimant_icn.present?
mpi_profile = mpi_service.find_profile_by_identifier(identifier: claimant_icn,
identifier_type: MPI::Constants::ICN)
@@ -241,6 +239,10 @@ def fetch_claimant
mpi_profile
end
+ def claimant_icn
+ @claimant_icn ||= form_attributes.dig('claimant', 'claimantId')
+ end
+
def disable_jobs?
Settings.claims_api&.poa_v2&.disable_jobs
end
diff --git a/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/request_controller.rb b/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/request_controller.rb
index 4065535db86..06d8091b8dd 100644
--- a/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/request_controller.rb
+++ b/modules/claims_api/app/controllers/claims_api/v2/veterans/power_of_attorney/request_controller.rb
@@ -29,16 +29,15 @@ def index
validate_filter!(filter)
- service = ClaimsApi::ManageRepresentativeService.new(external_uid: 'power_of_attorney_request_uid',
- external_key: 'power_of_attorney_request_key')
+ service = ClaimsApi::PowerOfAttorneyRequestService::Index.new(poa_codes:, page_size:, page_index:, filter:)
- res = service.read_poa_request(poa_codes:, page_size:, page_index:, filter:)
-
- poa_list = res['poaRequestRespondReturnVOList']
+ poa_list = service.get_poa_list
raise Common::Exceptions::Lighthouse::BadGateway unless poa_list
- render json: Array.wrap(poa_list), status: :ok
+ render json: ClaimsApi::V2::Blueprints::PowerOfAttorneyRequestBlueprint.render(
+ poa_list, view: :index, root: :data
+ ), status: :ok
end
def decide
@@ -49,8 +48,8 @@ def decide
validate_decide_params!(proc_id:, decision:)
- service = ManageRepresentativeService.new(external_uid: 'power_of_attorney_request_uid',
- external_key: 'power_of_attorney_request_key')
+ service = ClaimsApi::ManageRepresentativeService.new(external_uid: Settings.bgs.external_uid,
+ external_key: Settings.bgs.external_key)
if decision == 'declined'
poa_request = validate_ptcpnt_id!(ptcpnt_id:, proc_id:, representative_id:, service:)
@@ -99,11 +98,12 @@ def create # rubocop:disable Metrics/MethodLength
veteran_icn: params[:veteranId],
claimant_icn:, poa_code:)
form_attributes['id'] = poa_request.id
- form_attributes['type'] = 'power-of-attorney-request'
end
# return only the form information consumers provided
- render json: { data: { attributes: form_attributes } }, status: :created
+ render json: ClaimsApi::V2::Blueprints::PowerOfAttorneyRequestBlueprint.render(form_attributes, view: :create,
+ root: :data),
+ status: :created
end
private
diff --git a/modules/claims_api/app/controllers/concerns/claims_api/disability_compensation_validations.rb b/modules/claims_api/app/controllers/concerns/claims_api/disability_compensation_validations.rb
index 0d73d59f291..e5f86145ea7 100644
--- a/modules/claims_api/app/controllers/concerns/claims_api/disability_compensation_validations.rb
+++ b/modules/claims_api/app/controllers/concerns/claims_api/disability_compensation_validations.rb
@@ -411,30 +411,44 @@ def validate_form_526_disability_secondary_disabilities!
end
def validate_form_526_disability_classification_code!
- return if (form_attributes['disabilities'].pluck('classificationCode') - [nil]).blank?
+ form_attributes['disabilities'].each_with_index do |disability, index|
+ classification_code = disability['classificationCode']
+ next if classification_code.nil? || classification_code.blank?
+
+ if bgs_classification_ids.include?(classification_code)
+ validate_form_526_disability_classification_code_end_date!(classification_code, index)
+ else
+ raise ::Common::Exceptions::InvalidFieldValue.new("disabilities.#{index}.classificationCode",
+ classification_code)
+ end
+ end
+ end
- form_attributes['disabilities'].each do |disability|
- next if disability['classificationCode'].blank?
- next if bgs_classification_ids.include?(disability['classificationCode'])
+ def validate_form_526_disability_classification_code_end_date!(classification_code, index)
+ bgs_disability = contention_classification_type_code_list.find { |d| d[:clsfcn_id] == classification_code }
+ end_date = bgs_disability[:end_dt] if bgs_disability
- raise ::Common::Exceptions::InvalidFieldValue.new('disabilities.classificationCode',
- disability['classificationCode'])
- end
+ return if end_date.nil?
+
+ return if Date.parse(end_date) >= Time.zone.today
+
+ raise ::Common::Exceptions::InvalidFieldValue.new("disabilities.#{index}.classificationCode", classification_code)
+ end
+
+ def contention_classification_type_code_list
+ @contention_classification_type_code_list ||= if Flipper.enabled?(:claims_api_526_validations_v1_local_bgs)
+ service = ClaimsApi::StandardDataService.new(
+ external_uid: Settings.bgs.external_uid,
+ external_key: Settings.bgs.external_key
+ )
+ service.get_contention_classification_type_code_list
+ else
+ bgs_service.data.get_contention_classification_type_code_list
+ end
end
def bgs_classification_ids
- return @bgs_classification_ids if @bgs_classification_ids.present?
-
- contention_classification_type_codes = if Flipper.enabled?(:claims_api_526_validations_v1_local_bgs)
- contention_service = ClaimsApi::StandardDataService.new(
- external_uid: Settings.bgs.external_uid,
- external_key: Settings.bgs.external_key
- )
- contention_service.get_contention_classification_type_code_list
- else
- bgs_service.data.get_contention_classification_type_code_list
- end
- @bgs_classification_ids = contention_classification_type_codes.pluck(:clsfcn_id)
+ contention_classification_type_code_list.pluck(:clsfcn_id)
end
def validate_form_526_disability_approximate_begin_date!
diff --git a/modules/claims_api/app/controllers/concerns/claims_api/v2/claims_requests/tracked_items.rb b/modules/claims_api/app/controllers/concerns/claims_api/v2/claims_requests/tracked_items.rb
index 226fcb98e8e..e160cf6de8a 100644
--- a/modules/claims_api/app/controllers/concerns/claims_api/v2/claims_requests/tracked_items.rb
+++ b/modules/claims_api/app/controllers/concerns/claims_api/v2/claims_requests/tracked_items.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require 'bgs_service/tracked_item_service'
module ClaimsApi
module V2
module ClaimsRequests
@@ -9,7 +10,7 @@ module TrackedItems
def find_tracked_items!(claim_id)
return if claim_id.blank?
- @tracked_items = local_bgs_service.find_tracked_items(claim_id)[:dvlpmt_items] || []
+ @tracked_items = tracked_item_service.find_tracked_items(claim_id)[:dvlpmt_items] || []
end
def find_tracked_item(id)
@@ -100,6 +101,15 @@ def build_tracked_item(tracked_item, status, item, wwsnfy: false)
uploads_allowed:
}
end
+
+ private
+
+ def tracked_item_service
+ @tracked_item_service ||= ClaimsApi::TrackedItemService.new(
+ external_uid: target_veteran.participant_id,
+ external_key: target_veteran.participant_id
+ )
+ end
end
end
end
diff --git a/modules/claims_api/app/services/claims_api/power_of_attorney_request_service/index.rb b/modules/claims_api/app/services/claims_api/power_of_attorney_request_service/index.rb
new file mode 100644
index 00000000000..f48b29586d5
--- /dev/null
+++ b/modules/claims_api/app/services/claims_api/power_of_attorney_request_service/index.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module ClaimsApi
+ module PowerOfAttorneyRequestService
+ class Index
+ def initialize(poa_codes:, page_size:, page_index:, filter: {})
+ @poa_codes = poa_codes
+ @page_size = page_size
+ @page_index = page_index
+ @filter = filter
+ end
+
+ def get_poa_list
+ proc_ids = poa_list.pluck('procID')
+
+ poa_requests = ClaimsApi::PowerOfAttorneyRequest.where(proc_id: proc_ids).select(:id, :proc_id)
+ poa_requests_by_proc_id = poa_requests.each_with_object({}) do |request, hash|
+ hash[request.proc_id] = request.id
+ end
+
+ poa_list.map do |poa_request|
+ proc_id = poa_request['procID']
+ poa_request['id'] = poa_requests_by_proc_id[proc_id]
+
+ poa_request
+ end
+ end
+
+ private
+
+ def poa_list
+ @poa_list ||= manage_representative_service.read_poa_request(poa_codes: @poa_codes, page_size: @page_size,
+ page_index: @page_index, filter: @filter)
+
+ @poa_list['poaRequestRespondReturnVOList']
+ end
+
+ def manage_representative_service
+ ClaimsApi::ManageRepresentativeService.new(external_uid: Settings.bgs.external_uid,
+ external_key: Settings.bgs.external_key)
+ end
+ 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 2cace86cb5f..ea74a05e1de 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
@@ -4,7 +4,7 @@ module ClaimsApi
class PoaAssignDependentClaimantJob < ClaimsApi::ServiceBase
LOG_TAG = 'poa_assign_dependent_claimant_job'
- def perform(poa_id)
+ def perform(poa_id, rep_id)
poa = ClaimsApi::PowerOfAttorney.find(poa_id)
service = dependent_claimant_poa_assignment_service(
@@ -29,6 +29,8 @@ def perform(poa_id)
poa.id,
'POA assigned for dependent'
)
+
+ ClaimsApi::VANotifyAcceptedJob.perform_async(poa.id, rep_id) if vanotify?(poa.auth_headers, rep_id)
end
private
diff --git a/modules/claims_api/app/sidekiq/claims_api/poa_updater.rb b/modules/claims_api/app/sidekiq/claims_api/poa_updater.rb
index 2de49c6885f..acfa21c1eb3 100644
--- a/modules/claims_api/app/sidekiq/claims_api/poa_updater.rb
+++ b/modules/claims_api/app/sidekiq/claims_api/poa_updater.rb
@@ -6,7 +6,7 @@
module ClaimsApi
class PoaUpdater < ClaimsApi::ServiceBase
- def perform(power_of_attorney_id, rep = nil)
+ def perform(power_of_attorney_id, rep_id = nil)
poa_form = ClaimsApi::PowerOfAttorney.find(power_of_attorney_id)
ssn = poa_form.auth_headers['va_eauth_pnid']
@@ -23,7 +23,7 @@ def perform(power_of_attorney_id, rep = nil)
ClaimsApi::Logger.log('poa', poa_id: poa_form.id, detail: 'BIRLS Success')
- ClaimsApi::VANotifyAcceptedJob.perform_async(poa_form.id, rep) if vanotify?(poa_form.auth_headers, rep)
+ ClaimsApi::VANotifyAcceptedJob.perform_async(poa_form.id, rep_id) if vanotify?(poa_form.auth_headers, rep_id)
ClaimsApi::PoaVBMSUpdater.perform_async(poa_form.id)
else
@@ -37,14 +37,6 @@ def perform(power_of_attorney_id, rep = nil)
private
- def vanotify?(auth_headers, rep)
- if Flipper.enabled?(:lighthouse_claims_api_v2_poa_va_notify)
- auth_headers.key?(ClaimsApi::V2::Veterans::PowerOfAttorney::BaseController::VA_NOTIFY_KEY) && rep.present?
- else
- false
- end
- end
-
def bgs_ext_service(poa_form)
BGS::Services.new(
external_uid: poa_form.external_uid,
diff --git a/modules/claims_api/app/sidekiq/claims_api/service_base.rb b/modules/claims_api/app/sidekiq/claims_api/service_base.rb
index 326265f8306..318cc4ff5d1 100644
--- a/modules/claims_api/app/sidekiq/claims_api/service_base.rb
+++ b/modules/claims_api/app/sidekiq/claims_api/service_base.rb
@@ -227,6 +227,12 @@ def extract_poa_code(poa_form_data)
end
end
+ def vanotify?(auth_headers, rep_id)
+ rep = ::Veteran::Service::Representative.where(representative_id: rep_id).order(created_at: :desc).first
+ Flipper.enabled?(:lighthouse_claims_api_v2_poa_va_notify) &&
+ auth_headers.key?(ClaimsApi::V2::Veterans::PowerOfAttorney::BaseController::VA_NOTIFY_KEY) && rep.present?
+ end
+
def evss_mapper_service(auto_claim)
ClaimsApi::V2::DisabilityCompensationEvssMapper.new(auto_claim)
end
diff --git a/modules/claims_api/app/sidekiq/claims_api/v2/poa_form_builder_job.rb b/modules/claims_api/app/sidekiq/claims_api/v2/poa_form_builder_job.rb
index 6e5eea0c0ef..1c31255e303 100644
--- a/modules/claims_api/app/sidekiq/claims_api/v2/poa_form_builder_job.rb
+++ b/modules/claims_api/app/sidekiq/claims_api/v2/poa_form_builder_job.rb
@@ -32,9 +32,9 @@ def perform(power_of_attorney_id, form_number, rep_id, action)
end
if dependent_filing?(power_of_attorney)
- ClaimsApi::PoaAssignDependentClaimantJob.perform_async(power_of_attorney.id)
+ ClaimsApi::PoaAssignDependentClaimantJob.perform_async(power_of_attorney.id, rep_id)
else
- ClaimsApi::PoaUpdater.perform_async(power_of_attorney.id, rep)
+ ClaimsApi::PoaUpdater.perform_async(power_of_attorney.id, rep_id)
end
rescue VBMS::Unknown
rescue_vbms_error(power_of_attorney)
diff --git a/modules/claims_api/app/sidekiq/claims_api/va_notify_accepted_job.rb b/modules/claims_api/app/sidekiq/claims_api/va_notify_accepted_job.rb
index 2710975d28c..12c8a444c71 100644
--- a/modules/claims_api/app/sidekiq/claims_api/va_notify_accepted_job.rb
+++ b/modules/claims_api/app/sidekiq/claims_api/va_notify_accepted_job.rb
@@ -4,7 +4,7 @@ module ClaimsApi
class VANotifyAcceptedJob < ClaimsApi::ServiceBase
LOG_TAG = 'va_notify_accepted_job'
- def perform(poa_id, rep)
+ def perform(poa_id, rep_id)
return if skip_notification_email?
poa = ClaimsApi::PowerOfAttorney.find(poa_id)
@@ -14,10 +14,12 @@ def perform(poa_id, rep)
detail: "Could not find Power of Attorney with id: #{poa_id}"
)
end
+
if organization_filing?(poa.form_data)
org = find_org(poa, '2122')
res = send_organization_notification(poa, org)
else
+ rep = ::Veteran::Service::Representative.where(representative_id: rep_id).order(created_at: :desc).first
poa_code_from_form('2122a', poa)
res = send_representative_notification(poa, rep)
end
@@ -173,7 +175,7 @@ def build_address(line1, line2, line3)
end
def claimant_first_name(poa)
- poa.auth_headers['va_eauth_firstName']
+ poa.form_data.dig('claimant', 'firstName').presence || poa.auth_headers['va_eauth_firstName']
end
def organization_filing?(form_data)
diff --git a/modules/claims_api/app/swagger/claims_api/description/v2.md b/modules/claims_api/app/swagger/claims_api/description/v2.md
index 4304613d734..eb50e7bfb4d 100644
--- a/modules/claims_api/app/swagger/claims_api/description/v2.md
+++ b/modules/claims_api/app/swagger/claims_api/description/v2.md
@@ -1,6 +1,6 @@
## Background
-The Benefits Claims API Version 2 lets internal consumers:
+The Benefits Claims API Version 2 lets internal consumers:
- Retrieve existing claim information, including status, by claim ID.
- Automatically establish an Intent To File (21-0966) in VBMS.
@@ -11,7 +11,7 @@ The Benefits Claims API Version 2 lets internal consumers:
- Automatically establish a power of attorney appointment in VBMS for an accredited individual (VA Form 21-22a).
You should use the [Benefits Claims API Version 1](https://developer.va.gov/explore/benefits/docs/claims?version=current) if you are a consumer outside of VA and do not have the necessary VA agreements to use this API.
-
+
## Appointing an accredited representative for dependents
Dependents 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.
@@ -28,14 +28,14 @@ End-to-end claims tracking provides the status of claims as they move through th
### Claim statuses
-After you submit a disability compensation claim with the `POST /veterans/{veteranId}/526/synchronous` endpoint, it is then established in Veterans Benefits Management System (VBMS). A `202` response means that the claim was successfully submitted by the API. However, it does not mean VA has received the required 526EZ PDF.
+After you submit a disability compensation claim with the `POST /veterans/{veteranId}/526/synchronous` endpoint, it is then established in Veterans Benefits Management System (VBMS). A `202` response means that the claim was successfully submitted by the API. However, it does not mean VA has received the required 526EZ PDF.
-To confirm the status of your submission, use the `GET /veterans/{veteranId}/claims/{id}` endpoint and the ID returned with your submission response. Statuses are:
+To confirm the status of your submission, use the `GET /veterans/{veteranId}/claims/{id}` endpoint and the ID returned with your submission response. Statuses are:
* **Pending**: The claim is successfully submitted for processing
* **Errored**: The submission encountered upstream errors
-* **Canceled**: The claim was identified as a duplicate, or another issue caused the claim to be canceled.
- * For duplicate claims, the claim's progress is tracked under a different Claim ID than the one returned in your submission response.
+* **Canceled**: The claim was identified as a duplicate, or another issue caused the claim to be canceled.
+ * For duplicate claims, the claim's progress is tracked under a different Claim ID than the one returned in your submission response.
* **Claim received**: The claim was received, but hasn't been assigned to a reviewer yet.
* **Initial review**: The claim has been assigned to a reviewer, who will determine if more information is needed.
* **Evidence gathering, review, and decision**: VA is gathering evidence to make a decision from health care providers, government agencies, and other sources.
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 6c7e7cf2b5f..f2cad251cd3 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
@@ -413,144 +413,221 @@
"description": "Search results",
"content": {
"application/json": {
- "example": [
- {
- "VSOUserEmail": null,
- "VSOUserFirstName": "Joe",
- "VSOUserLastName": "BestRep",
- "changeAddressAuth": "Y",
- "claimantCity": "Charlottesville",
- "claimantCountry": "USA",
- "claimantMilitaryPO": null,
- "claimantMilitaryPostalCode": null,
- "claimantState": null,
- "claimantZip": "00123",
- "dateRequestActioned": "2024-05-25T13:45:00-05:00",
- "dateRequestReceived": "2012-11-23T16:49:16-06:00",
- "declinedReason": null,
- "healthInfoAuth": "Y",
- "poaCode": "083",
- "procID": "11027",
- "secondaryStatus": "New",
- "vetFirstName": "[Vet First Name]",
- "vetLastName": "[Vet Last Name]",
- "vetMiddleName": null,
- "vetPtcpntID": "111"
- },
- {
- "VSOUserEmail": null,
- "VSOUserFirstName": "VDC USER",
- "VSOUserLastName": null,
- "changeAddressAuth": "Y",
- "claimantCity": "USAG J",
- "claimantCountry": "USA",
- "claimantMilitaryPO": "APO",
- "claimantMilitaryPostalCode": "AP",
- "claimantState": null,
- "claimantZip": "01234",
- "dateRequestActioned": "2013-01-14T08:50:17-06:00",
- "dateRequestReceived": "2013-01-14T08:50:17-06:00",
- "declinedReason": null,
- "healthInfoAuth": "Y",
- "poaCode": "002",
- "procID": "10906",
- "secondaryStatus": "New",
- "vetFirstName": "first",
- "vetLastName": "last",
- "vetMiddleName": null,
- "vetPtcpntID": "111"
- },
- {
- "VSOUserEmail": null,
- "VSOUserFirstName": "VDC USER",
- "VSOUserLastName": null,
- "changeAddressAuth": "Y",
- "claimantCity": "Bourges",
- "claimantCountry": "France",
- "claimantMilitaryPO": null,
- "claimantMilitaryPostalCode": null,
- "claimantState": null,
- "claimantZip": "00123",
- "dateRequestActioned": "2013-01-14T08:51:25-06:00",
- "dateRequestReceived": "2013-01-14T08:51:25-06:00",
- "declinedReason": null,
- "healthInfoAuth": "Y",
- "poaCode": "002",
- "procID": "10907",
- "secondaryStatus": "New",
- "vetFirstName": "first",
- "vetLastName": "last",
- "vetMiddleName": null,
- "vetPtcpntID": "111"
- }
- ],
+ "example": {
+ "data": [
+ {
+ "id": null,
+ "type": "power-of-attorney-request",
+ "attributes": {
+ "veteran": {
+ "firstName": "[Vet First Name]",
+ "lastName": "[Vet Last Name]",
+ "middleName": null
+ },
+ "claimant": {
+ "city": "Charlottesville",
+ "country": "USA",
+ "militaryPo": null,
+ "militaryPostalCode": null,
+ "state": null,
+ "zip": "00123"
+ },
+ "representative": {
+ "poaCode": "083",
+ "vsoUserEmail": null,
+ "vsoUserFirstName": "Joe",
+ "vsoUserLastName": "BestRep"
+ },
+ "receivedDate": "2012-11-23T16:49:16-06:00",
+ "actionedDate": "2024-05-25T13:45:00-05:00",
+ "status": "New",
+ "declinedReason": null,
+ "changeAddressAuthorization": "Y",
+ "healthInfoAuthorization": "Y"
+ }
+ },
+ {
+ "id": null,
+ "type": "power-of-attorney-request",
+ "attributes": {
+ "veteran": {
+ "firstName": "first",
+ "lastName": "last",
+ "middleName": null
+ },
+ "claimant": {
+ "city": "USAG J",
+ "country": "USA",
+ "militaryPo": "APO",
+ "militaryPostalCode": "AP",
+ "state": null,
+ "zip": "01234"
+ },
+ "representative": {
+ "poaCode": "002",
+ "vsoUserEmail": null,
+ "vsoUserFirstName": "VDC USER",
+ "vsoUserLastName": null
+ },
+ "receivedDate": "2013-01-14T08:50:17-06:00",
+ "actionedDate": "2013-01-14T08:50:17-06:00",
+ "status": "New",
+ "declinedReason": null,
+ "changeAddressAuthorization": "Y",
+ "healthInfoAuthorization": "Y"
+ }
+ },
+ {
+ "id": null,
+ "type": "power-of-attorney-request",
+ "attributes": {
+ "veteran": {
+ "firstName": "first",
+ "lastName": "last",
+ "middleName": null
+ },
+ "claimant": {
+ "city": "Bourges",
+ "country": "France",
+ "militaryPo": null,
+ "militaryPostalCode": null,
+ "state": null,
+ "zip": "00123"
+ },
+ "representative": {
+ "poaCode": "002",
+ "vsoUserEmail": null,
+ "vsoUserFirstName": "VDC USER",
+ "vsoUserLastName": null
+ },
+ "receivedDate": "2013-01-14T08:51:25-06:00",
+ "actionedDate": "2013-01-14T08:51:25-06:00",
+ "status": "New",
+ "declinedReason": null,
+ "changeAddressAuthorization": "Y",
+ "healthInfoAuthorization": "Y"
+ }
+ }
+ ]
+ },
"schema": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "VSOUserEmail": {
- "type": "string"
- },
- "VSOUserFirstName": {
- "type": "string"
- },
- "VSOUserLastName": {
- "type": "string"
- },
- "changeAddressAuth": {
- "type": "string"
- },
- "claimantCity": {
- "type": "string"
- },
- "claimantCountry": {
- "type": "string"
- },
- "claimantMilitaryPO": {
- "type": "string"
- },
- "claimantMilitaryPostalCode": {
- "type": "string"
- },
- "claimantState": {
- "type": "string"
- },
- "claimantZip": {
- "type": "string"
- },
- "dateRequestActioned": {
- "type": "string"
- },
- "dateRequestReceived": {
- "type": "string"
- },
- "declinedReason": {
- "type": "string"
- },
- "healthInfoAuth": {
- "type": "string"
- },
- "poaCode": {
- "type": "string"
- },
- "procID": {
- "type": "string"
- },
- "secondaryStatus": {
- "type": "string"
- },
- "vetFirstName": {
- "type": "string"
- },
- "vetLastName": {
- "type": "string"
- },
- "vetMiddleName": {
- "type": "string"
- },
- "vetPtcpntID": {
- "type": "string"
+ "type": "object",
+ "required": [
+ "data"
+ ],
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "type",
+ "attributes"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "power-of-attorney-request"
+ ]
+ },
+ "attributes": {
+ "type": "object",
+ "required": [
+ "veteran",
+ "claimant",
+ "representative"
+ ],
+ "properties": {
+ "veteran": {
+ "type": "object",
+ "properties": {
+ "firstName": {
+ "type": "string"
+ },
+ "lastName": {
+ "type": "string"
+ },
+ "middleName": {
+ "type": "string"
+ }
+ }
+ },
+ "claimant": {
+ "type": "object",
+ "properties": {
+ "city": {
+ "type": "string"
+ },
+ "country": {
+ "type": "string"
+ },
+ "militaryPo": {
+ "type": "string"
+ },
+ "militaryPostalCode": {
+ "type": "string"
+ },
+ "state": {
+ "type": "string"
+ },
+ "zip": {
+ "type": "string"
+ }
+ }
+ },
+ "representative": {
+ "type": "object",
+ "properties": {
+ "poaCode": {
+ "type": "string"
+ },
+ "vsoUserEmail": {
+ "type": "string"
+ },
+ "vsoUserFirstName": {
+ "type": "string"
+ },
+ "vsoUserLastName": {
+ "type": "string"
+ }
+ }
+ },
+ "receivedDate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "actionedDate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "status": {
+ "type": "string"
+ },
+ "declinedReason": {
+ "type": "string"
+ },
+ "changeAddressAuthorization": {
+ "type": "string",
+ "enum": [
+ "Y",
+ "N"
+ ]
+ },
+ "healthInfoAuthorization": {
+ "type": "string",
+ "enum": [
+ "Y",
+ "N"
+ ]
+ }
+ }
+ }
+ }
}
}
}
@@ -13155,6 +13232,8 @@
"application/json": {
"example": {
"data": {
+ "id": "5f12b2a5-d865-43fd-8fad-9ac8ff65e110",
+ "type": "power-of-attorney-request",
"attributes": {
"veteran": {
"serviceNumber": "123678453",
@@ -13187,9 +13266,7 @@
"SICKLE_CELL",
"HIV",
"ALCOHOLISM"
- ],
- "id": "96fc2003-9ded-4a64-a7f2-798f3d018746",
- "type": "power-of-attorney-request"
+ ]
}
}
},
@@ -13204,9 +13281,22 @@
"type": "object",
"additionalProperties": false,
"required": [
+ "id",
+ "type",
"attributes"
],
"properties": {
+ "id": {
+ "type": "string",
+ "description": "The unique identifier for the power of attorney request"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "power-of-attorney-request"
+ ],
+ "description": "The type of the resource"
+ },
"attributes": {
"type": "object",
"additionalProperties": false,
@@ -13470,16 +13560,6 @@
"consentAddressChange": {
"description": "AUTHORIZATION FOR REPRESENTATIVE TO ACT ON CLAIMANT'S BEHALF TO CHANGE CLAIMANT'S ADDRESS.",
"type": "boolean"
- },
- "id": {
- "description": "The ID of the new Power of Attorney request.",
- "type": "string",
- "example": "12e13134-7229-4e44-90ae-bcea2a4525fa"
- },
- "type": {
- "description": "The type of Power of Attorney request.",
- "type": "string",
- "example": "power-of-attorney-request"
}
}
}
diff --git a/modules/claims_api/config/initializers/okcomputer.rb b/modules/claims_api/config/initializers/okcomputer.rb
index 5a5c8ff7d3e..faa98cecb00 100644
--- a/modules/claims_api/config/initializers/okcomputer.rb
+++ b/modules/claims_api/config/initializers/okcomputer.rb
@@ -136,24 +136,39 @@ def faraday_client
end
OkComputer::Registry.register 'mpi', MpiCheck.new
-OkComputer::Registry.register 'localbgs-claimant',
- FaradayBGSCheck.new('ClaimantServiceBean/ClaimantWebService')
-OkComputer::Registry.register 'localbgs-corporate_update',
- FaradayBGSCheck.new('CorporateUpdateServiceBean/CorporateUpdateWebService')
-OkComputer::Registry.register 'localbgs-person',
- FaradayBGSCheck.new('PersonWebServiceBean/PersonWebService')
+OkComputer::Registry.register 'form-526-docker-container',
+ Form526DockerContainerCheck.new('wss-form526-services-web/tools/version.jsp')
+OkComputer::Registry.register 'pdf-generator', PDFGenratorCheck.new('form-526ez-pdf-generator/actuator/health')
OkComputer::Registry.register 'localbgs-org',
FaradayBGSCheck.new('OrgWebServiceBean/OrgWebService')
-# rubocop:disable Layout/LineLength
-OkComputer::Registry.register 'localbgs-ebenefitsbenftclaim',
- FaradayBGSCheck.new('EBenefitsBnftClaimStatusWebServiceBean/EBenefitsBnftClaimStatusWebService')
-# rubocop:enable Layout/LineLength
-OkComputer::Registry.register 'localbgs-intenttofile',
- FaradayBGSCheck.new('IntentToFileWebServiceBean/IntentToFileWebService')
OkComputer::Registry.register 'localbgs-trackeditem',
FaradayBGSCheck.new('TrackedItemService/TrackedItemService')
-OkComputer::Registry.register 'benefits-documents',
- BeneftsDocumentsCheck.new('services/benefits-documents/v1/healthcheck')
-OkComputer::Registry.register 'form-526-docker-container',
- Form526DockerContainerCheck.new('wss-form526-services-web/tools/version.jsp')
-OkComputer::Registry.register 'pdf-generator', PDFGenratorCheck.new('form-526ez-pdf-generator/actuator/health')
+
+services = [
+ { name: 'benefit_claim_service', endpoint: 'BenefitClaimServiceBean/BenefitClaimWebService' },
+ { name: 'claimant_web_service', endpoint: 'ClaimManagementService/ClaimManagementService' },
+ { name: 'claim_management_service', endpoint: 'ClaimantServiceBean/ClaimantWebService' },
+ { name: 'contention_service', endpoint: 'ContentionService/ContentionService' },
+ { name: 'corporate_update_web_service', endpoint: 'CorporateUpdateServiceBean/CorporateUpdateService' },
+ { name: 'e_benefits_bnft_claim_status_web_service',
+ endpoint: 'EBenefitsBnftClaimStatusWebServiceBean/EBenefitsBnftClaimStatusWebService' },
+ { name: 'intent_to_file_web_service', endpoint: 'IntentToFileWebServiceBean/IntentToFileWebService' },
+ { name: 'manage_representative_service', endpoint: 'VDC/ManageRepresentativeService' },
+ { name: 'person_web_service', endpoint: 'PersonWebServiceBean/PersonWebService' },
+ { name: 'standard_data_service', endpoint: 'StandardDataService/StandardDataService' },
+ { name: 'vet_record_web_service', endpoint: 'VetRecordServiceBean/VetRecordWebService' },
+ { name: 'veteran_representative_service', endpoint: 'VDC/VeteranRepresentativeService' },
+ { name: 'vnp_atchms_service', endpoint: 'VnpAtchmsWebServiceBean/VnpAtchmsService' },
+ { name: 'vnp_person_service', endpoint: 'VnpPersonWebServiceBean/VnpPersonService' },
+ { name: 'vnp_proc_form_service', endpoint: 'VnpProcFormWebServiceBean/VnpProcFormService' },
+ { name: 'vnp_proc_service_v2', endpoint: 'VnpProcWebServiceBeanV2/VnpProcServiceV2' },
+ { name: 'vnp_ptcpnt_addrs_service', endpoint: 'VnpPtcpntAddrsWebServiceBean/VnpPtcpntAddrsService' },
+ { name: 'vnp_ptcpnt_phone_service', endpoint: 'VnpPtcpntPhoneWebServiceBean/VnpPtcpntPhoneService' },
+ { name: 'vnp_ptcpnt_service', endpoint: 'VnpPtcpntWebServiceBean/VnpPtcpntService' },
+ { name: 'org_web_service', endpoint: 'OrgWebServiceBean/OrgWebService' },
+ { name: 'standard_data_web_service', endpoint: 'StandardDataWebServiceBean/StandardDataWebService' },
+ { name: 'tracked_item_service', endpoint: 'TrackedItemService/TrackedItemService' }
+]
+services.each do |service|
+ OkComputer::Registry.register service[:name], FaradayBGSCheck.new(service[:endpoint])
+end
diff --git a/modules/claims_api/lib/bgs_service/local_bgs.rb b/modules/claims_api/lib/bgs_service/local_bgs.rb
index b6664b92e09..cc568b370a2 100644
--- a/modules/claims_api/lib/bgs_service/local_bgs.rb
+++ b/modules/claims_api/lib/bgs_service/local_bgs.rb
@@ -89,32 +89,6 @@ def find_poa_by_participant_id(id)
key: 'return')
end
- def find_poa_history_by_ptcpnt_id(id)
- body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
-
- EOXML
-
- { ptcpntId: id }.each do |k, v|
- body.xpath("./*[local-name()='#{k}']")[0].content = v
- end
-
- make_request(endpoint: 'OrgWebServiceBean/OrgWebService', action: 'findPoaHistoryByPtcpntId', body:,
- key: 'PoaHistory')
- end
-
- def find_tracked_items(id)
- body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
-
- EOXML
-
- { claimId: id }.each do |k, v|
- body.xpath("./*[local-name()='#{k}']")[0].content = v
- end
-
- make_request(endpoint: 'TrackedItemService/TrackedItemService', action: 'findTrackedItems', body:,
- key: 'BenefitClaim')
- end
-
def header # rubocop:disable Metrics/MethodLength
# Stock XML structure {{{
header = Nokogiri::XML::DocumentFragment.parse <<~EOXML
diff --git a/modules/claims_api/lib/bgs_service/local_bgs_proxy.rb b/modules/claims_api/lib/bgs_service/local_bgs_proxy.rb
deleted file mode 100644
index ae75b328e93..00000000000
--- a/modules/claims_api/lib/bgs_service/local_bgs_proxy.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-require 'bgs_service/local_bgs_refactored'
-require 'bgs_service/local_bgs'
-
-module ClaimsApi
- # Proxy class that switches at runtime between using `LocalBGS` and
- # `LocalBGSRefactored` depending on the value of our feature toggle.
- class LocalBGSProxy
- legacy_ancestors =
- LocalBGS.ancestors -
- LocalBGSRefactored.ancestors
-
- legacy_api =
- legacy_ancestors.flat_map do |ancestor|
- ancestor.instance_methods(false) - [:initialize]
- end
-
- refactored_ancestors =
- LocalBGSRefactored.ancestors -
- LocalBGS.ancestors
-
- refactored_api =
- refactored_ancestors.flat_map do |ancestor|
- ancestor.instance_methods(false) - [:initialize]
- end
-
- # This makes the assumption that we'll maintain compatibility for callers of
- # `LocalBGS` by considering only its public instance methods, and in
- # particular those not installed by framework-level ancestors. A "one-time"
- # check was performed to ensure that instance methods that callers invoke
- # directly are contained in `common_api` and not contained in `missing_api`.
- missing_api = legacy_api - refactored_api
- common_api = legacy_api & refactored_api
-
- Rails.logger.trace(
- "Comparison between LocalBGS and LocalBGSRefactored API's",
- missing_api:,
- common_api:
- )
-
- class << self
- delegate :breakers_service, to: :get_proxied_klass
-
- def get_proxied_klass
- if Flipper.enabled?(:claims_api_local_bgs_refactor)
- LocalBGSRefactored
- else
- LocalBGS
- end
- end
- end
-
- delegate(*common_api, to: :proxied)
- attr_reader :proxied
-
- def initialize(...)
- @proxied = self.class.get_proxied_klass.new(...)
- end
- end
-end
diff --git a/modules/claims_api/lib/bgs_service/local_bgs_refactored.rb b/modules/claims_api/lib/bgs_service/local_bgs_refactored.rb
index 17c8363b5e4..397fbdb8032 100644
--- a/modules/claims_api/lib/bgs_service/local_bgs_refactored.rb
+++ b/modules/claims_api/lib/bgs_service/local_bgs_refactored.rb
@@ -2,53 +2,14 @@
require 'claims_api/bgs_client'
require 'claims_api/bgs_client/definitions'
-require 'claims_api/bgs_client/error'
-require 'bgs_service/local_bgs_refactored/error_handler'
require 'bgs_service/local_bgs_refactored/find_definition'
-require 'bgs_service/local_bgs_refactored/miscellaneous'
module ClaimsApi
- ##
- # @deprecated Use {BGSClient.perform_request} instead. There ought to be a
- # clear separation between the single method that performs the transport to
- # BGS and any business logic that invokes said transport. By housing that
- # single method as an instance method of this class, we encouraged
- # business logic modules to inherit this class and then inevitably start to
- # conflate business logic back into the transport layer here. There was a
- # particularly easy temptation to put business object state validation as
- # well as the dumping and loading of business object state into this layer,
- # but that should live in the business logic layer and not here. Commentary
- # about deprecation reasons is enumerated inline with the implementation
- # below.
- ##
- # @deprecated Doesn't conform to Rails autoloading conventions, so we don't
- # get it autoloaded across the project.
- #
class LocalBGSRefactored
- ##
- # @deprecated This bag of miscellaneous behavior is meant for callers of
- # this, rather than being centralized here.
- include Miscellaneous
-
- BAD_GATEWAY_EXCEPTIONS = [
- BGSClient::Error::ConnectionFailed,
- BGSClient::Error::SSLError,
- ##
- # @deprecated According to VA API Standards, a timeout should correspond
- # to the HTTP error code for a gateway timeout, 504:
- # https://department-of-veterans-affairs.github.io/va-api-standards/errors/#choosing-an-error-code
- #
- BGSClient::Error::TimeoutError
- ].freeze
-
class << self
delegate :breakers_service, to: BGSClient
end
- ##
- # @deprecated Can use default named arguments. Not really a deprecation
- # reason.
- #
def initialize(external_uid:, external_key:)
external_uid ||= Settings.bgs.external_uid
external_key ||= Settings.bgs.external_key
@@ -59,72 +20,5 @@ def initialize(external_uid:, external_key:)
external_key:
)
end
-
- ##
- # @deprecated Prefer doing just transport against bundled BGS service action
- # definitions rather than wrapping them at higher abstraction layers.
- #
- def make_request( # rubocop:disable Metrics/MethodLength
- endpoint:, action:, body:, key: nil
- )
- action =
- FindDefinition.for_action(
- endpoint,
- action
- )
-
- request =
- BGSClient.const_get(:Request).new(
- action, external_id: @external_id
- )
-
- ##
- # @deprecated Every service action result always lives within a particular
- # key, so we can always extract the result rather than expose another
- # option to the caller.
- #
- # @deprecated Prefer composing XML documents with an XML builder. Other
- # approaches like templating into strings or immediately parsing and
- # injecting with open-ended xpath are inconvenient or error-prone.
- #
- result = request.perform { |xml| xml << body.to_s }
- result = { action.key => result }
- result = result[key].to_h if key.present?
-
- request.send(:log_duration, 'transformed_response') do
- ##
- # @deprecated Callers should be hydrating domain entities anyway, so
- # this transformation pass is wasteful.
- #
- result.deep_transform_keys! do |k|
- k.underscore.to_sym
- end
- end
-
- result
- rescue *BAD_GATEWAY_EXCEPTIONS
- ##
- # @deprecated We were determining our external interface extremely low in
- # the stack by raising one of our externally facing application errors
- # here.
- #
- raise ::Common::Exceptions::BadGateway
- rescue BGSClient::Error::BGSFault => e
- ##
- # @deprecated This error handler handles the logic for multiple different
- # callers rather than those callers handling their own logic.
- #
- ErrorHandler.handle_errors!(e)
- {}
- end
-
- ##
- # @deprecated Prefer doing just transport against bundled BGS service action
- # definitions rather than wrapping them at higher abstraction layers.
- #
- def healthcheck(endpoint)
- service = FindDefinition.for_service(endpoint)
- BGSClient.healthcheck(service)
- end
end
end
diff --git a/modules/claims_api/lib/bgs_service/org_web_service.rb b/modules/claims_api/lib/bgs_service/org_web_service.rb
new file mode 100644
index 00000000000..63e20c445ef
--- /dev/null
+++ b/modules/claims_api/lib/bgs_service/org_web_service.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module ClaimsApi
+ class OrgWebService < ClaimsApi::LocalBGS
+ def bean_name
+ 'OrgWebServiceBean/OrgWebService'
+ end
+
+ def find_poa_history_by_ptcpnt_id(id)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+
+ EOXML
+
+ { ptcpntId: id }.each do |k, v|
+ body.xpath("./*[local-name()='#{k}']")[0].content = v
+ end
+
+ make_request(endpoint: bean_name, action: 'findPoaHistoryByPtcpntId', body:,
+ key: 'PoaHistory')
+ end
+ end
+end
diff --git a/modules/claims_api/lib/bgs_service/tracked_item_service.rb b/modules/claims_api/lib/bgs_service/tracked_item_service.rb
new file mode 100644
index 00000000000..9b55111e246
--- /dev/null
+++ b/modules/claims_api/lib/bgs_service/tracked_item_service.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module ClaimsApi
+ class TrackedItemService < ClaimsApi::LocalBGS
+ def bean_name
+ 'TrackedItemService/TrackedItemService'
+ end
+
+ def find_tracked_items(id)
+ body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
+ #{id}
+ EOXML
+
+ make_request(endpoint: 'TrackedItemService/TrackedItemService', action: 'findTrackedItems', body:,
+ key: 'BenefitClaim')
+ end
+ end
+end
diff --git a/modules/claims_api/lib/bgs_service/veteran_representative_service.rb b/modules/claims_api/lib/bgs_service/veteran_representative_service.rb
index 2ef438d9482..3d37d0de7da 100644
--- a/modules/claims_api/lib/bgs_service/veteran_representative_service.rb
+++ b/modules/claims_api/lib/bgs_service/veteran_representative_service.rb
@@ -5,6 +5,10 @@
module ClaimsApi
class VeteranRepresentativeService < ClaimsApi::LocalBGS
+ def bean_name
+ 'VDC/VeteranRepresentativeService'
+ end
+
private
def make_request(namespace:, **args)
diff --git a/modules/claims_api/lib/bgs_service/vnp_atchms_service.rb b/modules/claims_api/lib/bgs_service/vnp_atchms_service.rb
index 548350ef459..5fb1b43c920 100644
--- a/modules/claims_api/lib/bgs_service/vnp_atchms_service.rb
+++ b/modules/claims_api/lib/bgs_service/vnp_atchms_service.rb
@@ -2,6 +2,10 @@
module ClaimsApi
class VnpAtchmsService < ClaimsApi::LocalBGS
+ def bean_name
+ 'VnpAtchmsWebServiceBean/VnpAtchmsService'
+ end
+
# Takes an object with a minimum of (other fields are camelized and passed to BGS):
# vnp_proc_id: BGS procID
# atchms_file_nm: File name
diff --git a/modules/claims_api/lib/bgs_service/vnp_person_service.rb b/modules/claims_api/lib/bgs_service/vnp_person_service.rb
index 3c47d73facb..a0d2c6c60bf 100644
--- a/modules/claims_api/lib/bgs_service/vnp_person_service.rb
+++ b/modules/claims_api/lib/bgs_service/vnp_person_service.rb
@@ -2,6 +2,10 @@
module ClaimsApi
class VnpPersonService < ClaimsApi::LocalBGS
+ def bean_name
+ 'VnpPersonWebServiceBean/VnpPersonService'
+ end
+
# Takes an object with a minimum of (other fields are camelized and passed to BGS):
# vnp_proc_id: BGS procID
# vnp_ptcpnt_id: Veteran's participant id
diff --git a/modules/claims_api/lib/bgs_service/vnp_proc_form_service.rb b/modules/claims_api/lib/bgs_service/vnp_proc_form_service.rb
index 807af6c3047..efe01f2a2ab 100644
--- a/modules/claims_api/lib/bgs_service/vnp_proc_form_service.rb
+++ b/modules/claims_api/lib/bgs_service/vnp_proc_form_service.rb
@@ -4,6 +4,10 @@ module ClaimsApi
class VnpProcFormService < ClaimsApi::LocalBGS
FORM_TYPE_CD = '21-22'
+ def bean_name
+ 'VnpProcFormWebServiceBean/VnpProcFormService'
+ end
+
def vnp_proc_form_create(options)
vnp_proc_id = options[:vnp_proc_id]
options.delete(:vnp_proc_id)
diff --git a/modules/claims_api/lib/bgs_service/vnp_proc_service_v2.rb b/modules/claims_api/lib/bgs_service/vnp_proc_service_v2.rb
index e4d9eb48b36..11b17ce75c8 100644
--- a/modules/claims_api/lib/bgs_service/vnp_proc_service_v2.rb
+++ b/modules/claims_api/lib/bgs_service/vnp_proc_service_v2.rb
@@ -4,6 +4,10 @@ module ClaimsApi
class VnpProcServiceV2 < ClaimsApi::LocalBGS
PROC_TYPE_CD = 'POAAUTHZ'
+ def bean_name
+ 'VnpProcWebServiceBeanV2/VnpProcServiceV2'
+ end
+
def vnp_proc_create
body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
diff --git a/modules/claims_api/lib/bgs_service/vnp_ptcpnt_addrs_service.rb b/modules/claims_api/lib/bgs_service/vnp_ptcpnt_addrs_service.rb
index 85bf4cda2ff..0e479c8df43 100644
--- a/modules/claims_api/lib/bgs_service/vnp_ptcpnt_addrs_service.rb
+++ b/modules/claims_api/lib/bgs_service/vnp_ptcpnt_addrs_service.rb
@@ -2,6 +2,10 @@
module ClaimsApi
class VnpPtcpntAddrsService < ClaimsApi::LocalBGS
+ def bean_name
+ 'VnpPtcpntAddrsWebServiceBean/VnpPtcpntAddrsService'
+ end
+
def vnp_ptcpnt_addrs_create(options)
arg_strg = convert_nil_values(options)
body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
diff --git a/modules/claims_api/lib/bgs_service/vnp_ptcpnt_phone_service.rb b/modules/claims_api/lib/bgs_service/vnp_ptcpnt_phone_service.rb
index 3621b59aa44..9b6b5fee5a4 100644
--- a/modules/claims_api/lib/bgs_service/vnp_ptcpnt_phone_service.rb
+++ b/modules/claims_api/lib/bgs_service/vnp_ptcpnt_phone_service.rb
@@ -5,6 +5,10 @@ class VnpPtcpntPhoneService < ClaimsApi::LocalBGS
# vnpPtcpntPhoneCreate - This service is used to create VONAPP participant phone information
DEFAULT_TYPE = 'Daytime' # Daytime and Nighttime are the allowed values
+ def bean_name
+ 'VnpPtcpntPhoneWebServiceBean/VnpPtcpntPhoneService'
+ end
+
def vnp_ptcpnt_phone_create(options)
request_body = construct_body(options)
body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
diff --git a/modules/claims_api/lib/bgs_service/vnp_ptcpnt_service.rb b/modules/claims_api/lib/bgs_service/vnp_ptcpnt_service.rb
index e87a72269a6..65db682dfed 100644
--- a/modules/claims_api/lib/bgs_service/vnp_ptcpnt_service.rb
+++ b/modules/claims_api/lib/bgs_service/vnp_ptcpnt_service.rb
@@ -3,6 +3,11 @@
module ClaimsApi
class VnpPtcpntService < ClaimsApi::LocalBGS
# vnpPtcpntCreate - This service is used to create VONAPP participant information
+ #
+ def bean_name
+ 'VnpPtcpntWebServiceBean/VnpPtcpntService'
+ end
+
def vnp_ptcpnt_create(options)
arg_strg = convert_nil_values(options)
body = Nokogiri::XML::DocumentFragment.parse <<~EOXML
diff --git a/modules/claims_api/lib/claims_api/v1/poa_pdf_constructor/base.rb b/modules/claims_api/lib/claims_api/v1/poa_pdf_constructor/base.rb
index 04991d393d0..475f2c5d669 100644
--- a/modules/claims_api/lib/claims_api/v1/poa_pdf_constructor/base.rb
+++ b/modules/claims_api/lib/claims_api/v1/poa_pdf_constructor/base.rb
@@ -142,6 +142,13 @@ def fill_pdf(data)
@page2_path = temp_path
end
+ # With how the PDF reads, we leave blank if included in the form data, all other scenarios they are checked
+ def set_limitation_of_consent_check_box(consent_limits, item)
+ return 1 if consent_limits.blank?
+
+ consent_limits.include?(item) ? 0 : 1
+ end
+
#
# Add provided signatures to pdf pages.
#
diff --git a/modules/claims_api/lib/claims_api/v1/poa_pdf_constructor/organization.rb b/modules/claims_api/lib/claims_api/v1/poa_pdf_constructor/organization.rb
index ddf9ff22d79..536035f20ee 100644
--- a/modules/claims_api/lib/claims_api/v1/poa_pdf_constructor/organization.rb
+++ b/modules/claims_api/lib/claims_api/v1/poa_pdf_constructor/organization.rb
@@ -46,10 +46,10 @@ def page2_options(data)
"#{base_form}.SocialSecurityNumber_SecondTwoNumbers[0]": data.dig('veteran', 'ssn')[3..4],
"#{base_form}.SocialSecurityNumber_LastFourNumbers[0]": data.dig('veteran', 'ssn')[5..8],
"#{base_form}.I_Authorize[1]": data['recordConsent'] == true ? 1 : 0,
- "#{base_form}.Drug_Abuse[0]": data['consentLimits'].present? && data['consentLimits'].include?('DRUG ABUSE') ? 1 : 0,
- "#{base_form}.Alcoholism_Or_Alcohol_Abuse[0]": data['consentLimits'].present? && data['consentLimits'].include?('ALCOHOLISM') ? 1 : 0,
- "#{base_form}.Infection_With_The_Human_Immunodeficiency_Virus_HIV[0]": data['consentLimits'].present? && data['consentLimits'].include?('HIV') ? 1 : 0,
- "#{base_form}.sicklecellanemia[0]": data['consentLimits'].present? && data['consentLimits'].include?('SICKLE CELL') ? 1 : 0,
+ "#{base_form}.Drug_Abuse[0]": set_limitation_of_consent_check_box(data['consentLimits'], 'DRUG_ABUSE'),
+ "#{base_form}.Alcoholism_Or_Alcohol_Abuse[0]": set_limitation_of_consent_check_box(data['consentLimits'], 'ALCOHOLISM'),
+ "#{base_form}.Infection_With_The_Human_Immunodeficiency_Virus_HIV[0]": set_limitation_of_consent_check_box(data['consentLimits'], 'HIV'),
+ "#{base_form}.sicklecellanemia[0]": set_limitation_of_consent_check_box(data['consentLimits'], 'SICKLE_CELL'),
"#{base_form}.I_Authorize[0]": data['consentAddressChange'] == true ? 1 : 0,
"#{base_form}.Date_Signed[0]": I18n.l(Time.zone.now.to_date, format: :va_form),
"#{base_form}.Date_Signed[1]": I18n.l(Time.zone.now.to_date, format: :va_form)
diff --git a/modules/claims_api/lib/claims_api/v2/poa_pdf_constructor/base.rb b/modules/claims_api/lib/claims_api/v2/poa_pdf_constructor/base.rb
index f83cf019206..e5219c0d577 100644
--- a/modules/claims_api/lib/claims_api/v2/poa_pdf_constructor/base.rb
+++ b/modules/claims_api/lib/claims_api/v2/poa_pdf_constructor/base.rb
@@ -130,6 +130,13 @@ def fill_pdf(data)
@page2_path = temp_path
end
+ # With how the PDF reads, we leave blank if included in the form data, all other scenarios they are checked
+ def set_limitation_of_consent_check_box(consent_limits, item)
+ return 1 if consent_limits.blank?
+
+ consent_limits.include?(item) ? 0 : 1
+ end
+
def insert_text_signatures(page_template, signatures)
return if signatures.nil?
diff --git a/modules/claims_api/lib/claims_api/v2/poa_pdf_constructor/organization.rb b/modules/claims_api/lib/claims_api/v2/poa_pdf_constructor/organization.rb
index 4c2251c1cf5..4f70d1844c3 100644
--- a/modules/claims_api/lib/claims_api/v2/poa_pdf_constructor/organization.rb
+++ b/modules/claims_api/lib/claims_api/v2/poa_pdf_constructor/organization.rb
@@ -47,10 +47,10 @@ def page2_options(data)
# Item 19
"#{base_form}.I_Authorize[1]": data['recordConsent'] == true ? 1 : 0,
# Item 20
- "#{base_form}.Drug_Abuse[0]": data['consentLimits'].present? && data['consentLimits'].include?('DRUG_ABUSE') ? 1 : 0,
- "#{base_form}.Alcoholism_Or_Alcohol_Abuse[0]": data['consentLimits'].present? && data['consentLimits'].include?('ALCOHOLISM') ? 1 : 0,
- "#{base_form}.Infection_With_The_Human_Immunodeficiency_Virus_HIV[0]": data['consentLimits'].present? && data['consentLimits'].include?('HIV') ? 1 : 0,
- "#{base_form}.sicklecellanemia[0]": data['consentLimits'].present? && data['consentLimits'].include?('SICKLE_CELL') ? 1 : 0,
+ "#{base_form}.Drug_Abuse[0]": set_limitation_of_consent_check_box(data['consentLimits'], 'DRUG_ABUSE'),
+ "#{base_form}.Alcoholism_Or_Alcohol_Abuse[0]": set_limitation_of_consent_check_box(data['consentLimits'], 'ALCOHOLISM'),
+ "#{base_form}.Infection_With_The_Human_Immunodeficiency_Virus_HIV[0]": set_limitation_of_consent_check_box(data['consentLimits'], 'HIV'),
+ "#{base_form}.sicklecellanemia[0]": set_limitation_of_consent_check_box(data['consentLimits'], 'SICKLE_CELL'),
# Item 21
"#{base_form}.I_Authorize[0]": data['consentAddressChange'] == true ? 1 : 0,
# Item 22B
diff --git a/modules/claims_api/spec/concerns/claims_api/v2/claims_requests/tracked_items_spec.rb b/modules/claims_api/spec/concerns/claims_api/v2/claims_requests/tracked_items_spec.rb
index 0d18fc9028b..f02a37a0171 100644
--- a/modules/claims_api/spec/concerns/claims_api/v2/claims_requests/tracked_items_spec.rb
+++ b/modules/claims_api/spec/concerns/claims_api/v2/claims_requests/tracked_items_spec.rb
@@ -1,13 +1,14 @@
# frozen_string_literal: true
require 'rails_helper'
+require 'bgs_service/tracked_item_service'
class FakeController
include ClaimsApi::V2::ClaimsRequests::TrackedItems
include ClaimsApi::V2::ClaimsRequests::TrackedItemsAssistance
- def local_bgs_service
- @local_bgs_service ||= ClaimsApi::LocalBGS.new(
+ def tracked_item_service
+ @tracked_item_service ||= ClaimsApi::TrackedItemService.new(
external_uid: target_veteran.participant_id,
external_key: target_veteran.participant_id
)
diff --git a/modules/claims_api/spec/controllers/v2/veterans/power_of_attorney/request_controller_spec.rb b/modules/claims_api/spec/controllers/v2/veterans/power_of_attorney/request_controller_spec.rb
index 3051a28075e..19a56c2cec4 100644
--- a/modules/claims_api/spec/controllers/v2/veterans/power_of_attorney/request_controller_spec.rb
+++ b/modules/claims_api/spec/controllers/v2/veterans/power_of_attorney/request_controller_spec.rb
@@ -36,7 +36,7 @@
index_request_with(poa_codes:, auth_header:)
expect(response).to have_http_status(:ok)
- expect(JSON.parse(response.body).size).to eq(3)
+ expect(JSON.parse(response.body)['data'].size).to eq(3)
end
end
end
@@ -384,8 +384,8 @@
create_request_with(veteran_id:, form_attributes:, auth_header:)
expect(response).to have_http_status(:created)
- expect(JSON.parse(response.body)['data']['attributes']['id']).not_to be_nil
- expect(JSON.parse(response.body)['data']['attributes']['type']).to eq('power-of-attorney-request')
+ expect(JSON.parse(response.body)['data']['id']).not_to be_nil
+ expect(JSON.parse(response.body)['data']['type']).to eq('power-of-attorney-request')
end
end
end
diff --git a/modules/claims_api/spec/lib/claims_api/local_bgs_proxy_spec.rb b/modules/claims_api/spec/lib/claims_api/local_bgs_proxy_spec.rb
deleted file mode 100644
index bafda836afc..00000000000
--- a/modules/claims_api/spec/lib/claims_api/local_bgs_proxy_spec.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require 'bgs_service/local_bgs_proxy'
-
-describe ClaimsApi::LocalBGSProxy do
- subject do
- described_class.new(
- external_uid: nil,
- external_key: nil
- )
- end
-
- expected_instance_methods = {
- convert_nil_values: %i[options],
- find_poa_by_participant_id: %i[id],
- find_poa_history_by_ptcpnt_id: %i[id],
- find_tracked_items: %i[id],
- healthcheck: %i[endpoint],
- jrn: %i[],
- make_request: [endpoint: nil, action: nil, body: nil],
- to_camelcase: [claim: nil],
- transform_bgs_claim_to_evss: %i[claim],
- transform_bgs_claims_to_evss: %i[claims],
- validate_opts!: %i[opts required_keys]
- }
-
- expected_instance_methods.each_value(&:freeze)
- expected_instance_methods.freeze
-
- it 'defines the correct set of instance methods' do
- actual = described_class.instance_methods(false) - [:proxied]
- expect(actual).to match_array(expected_instance_methods.keys)
- end
-
- describe 'claims_api_local_bgs_refactor feature toggling' do
- before do
- expect(Flipper).to(
- receive(:enabled?)
- .with(:claims_api_local_bgs_refactor)
- .and_return(toggle)
- )
- end
-
- define_singleton_method(:it_delegates_every_instance_method) do |to:|
- it "has a proxied of type #{to}" do
- expect(subject.proxied).to be_a(to)
- end
-
- expected_instance_methods.each do |meth, args|
- describe "when instance method is `#{meth}`" do
- it "delegates to `#{to}`" do
- if args.empty?
- expect(subject.proxied).to receive(meth).with(no_args).once
- subject.send(meth)
- else
- args = args.deep_dup
- kwargs = args.extract_options!
- expect(subject.proxied).to receive(meth).with(*args, **kwargs).once
- subject.send(meth, *args, **kwargs)
- end
- end
- end
- end
- end
-
- describe 'with refactor toggled off' do
- let(:toggle) { false }
-
- it_delegates_every_instance_method(
- to: ClaimsApi::LocalBGS
- )
- end
-
- describe 'with refactor toggled on' do
- let(:toggle) { true }
-
- it_delegates_every_instance_method(
- to: ClaimsApi::LocalBGSRefactored
- )
- end
- end
-end
diff --git a/modules/claims_api/spec/lib/claims_api/local_bgs_refactored_spec.rb b/modules/claims_api/spec/lib/claims_api/local_bgs_refactored_spec.rb
deleted file mode 100644
index 70f29262bf4..00000000000
--- a/modules/claims_api/spec/lib/claims_api/local_bgs_refactored_spec.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require 'bgs_service/local_bgs_proxy'
-require 'bgs_service/e_benefits_bnft_claim_status_web_service'
-
-describe ClaimsApi::EbenefitsBnftClaimStatusWebService do
- subject { described_class.new external_uid: 'xUid', external_key: 'xKey' }
-
- before do
- allow(Flipper).to(
- receive(:enabled?)
- .with(:claims_api_local_bgs_refactor)
- .and_return(true)
- )
- end
-
- let(:soap_error_handler) { ClaimsApi::LocalBGSRefactored::ErrorHandler }
-
- # Testing potential ways the current check could be tricked
- describe '#all' do
- let(:subject_instance) { subject }
- let(:id) { 12_343 }
- let(:error_message) { { error: 'Did not work', code: 'XXX' } }
- let(:bgs_unknown_error_message) { { error: 'Unexpected error' } }
- let(:empty_array) { [] }
-
- context 'when an error message gets returned it still does not pass the count check' do
- it 'returns an empty array' do
- expect(error_message.count).to eq(2) # trick the claims count check
- # error message should trigger return
- allow(subject_instance).to(
- receive(:find_benefit_claims_status_by_ptcpnt_id).with(id).and_return(error_message)
- )
- expect(subject.all(id)).to eq([]) # verify correct return
- end
- end
-
- context 'when claims come back as a hash instead of an array' do
- it 'casts the hash as an array' do
- VCR.use_cassette('claims_api/bgs/claims/claims_trimmed_down') do
- claims = subject_instance.find_benefit_claims_status_by_ptcpnt_id('600061742')
- claims[:benefit_claims_dto][:benefit_claim] = claims[:benefit_claims_dto][:benefit_claim][0]
- allow(subject_instance).to(
- receive(:find_benefit_claims_status_by_ptcpnt_id).with(id).and_return(claims)
- )
-
- begin
- ret = subject_instance.send(:transform_bgs_claims_to_evss, claims)
- expect(ret.class).to_be Array
- expect(ret.size).to eq 1
- rescue => e
- expect(e.message).not_to include 'no implicit conversion of Array into Hash'
- end
- end
- end
- end
-
- # Already being checked but based on an error seen just want to lock this in to ensure nothing gets missed
- context 'when an empty array gets returned it still does not pass the count check' do
- it 'returns an empty array' do
- # error message should trigger return
- allow(subject_instance).to(
- receive(:find_benefit_claims_status_by_ptcpnt_id).with(id).and_return(empty_array)
- )
- expect(subject.all(id)).to eq([]) # verify correct return
- end
- end
-
- context 'when an error message gets returns unknown' do
- it 'the soap error handler returns unprocessable' do
- allow(subject_instance).to receive(:make_request).with(endpoint: 'PersonWebServiceBean/PersonWebService',
- action: 'findPersonBySSN',
- body: Nokogiri::XML::DocumentFragment.new(
- Nokogiri::XML::Document.new
- ),
- key: 'PersonDTO').and_return(:bgs_unknown_error_message)
- begin
- allow(soap_error_handler).to receive(:handle_errors!)
- .with(:bgs_unknown_error_message).and_raise(Common::Exceptions::UnprocessableEntity)
- ret = soap_error_handler.send(:handle_errors!, :bgs_unknown_error_message)
- expect(ret.class).to_be Array
- expect(ret.size).to eq 1
- rescue => e
- expect(e.message).to include 'Unprocessable Entity'
- end
- end
- end
- end
-end
diff --git a/modules/claims_api/spec/lib/claims_api/shared_pdf_constructor_base_examples_spec.rb b/modules/claims_api/spec/lib/claims_api/shared_pdf_constructor_base_examples_spec.rb
new file mode 100644
index 00000000000..85a3cf2814d
--- /dev/null
+++ b/modules/claims_api/spec/lib/claims_api/shared_pdf_constructor_base_examples_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'shared pdf constructor base behavior' do
+ it 'select all the boxes when nothing is added on the form' do
+ result = pdf_constructor_instance.send(:set_limitation_of_consent_check_box, nil, 'DRUG_ABUSE')
+ expect(result).to eq(1)
+ end
+
+ it 'does not select the box for an added consent limit' do
+ result = pdf_constructor_instance.send(:set_limitation_of_consent_check_box, ['HIV'], 'HIV')
+ expect(result).to eq(0)
+ end
+end
diff --git a/modules/claims_api/spec/lib/claims_api/v1/poa_pdf_constructor/base_spec.rb b/modules/claims_api/spec/lib/claims_api/v1/poa_pdf_constructor/base_spec.rb
new file mode 100644
index 00000000000..e25c7f58feb
--- /dev/null
+++ b/modules/claims_api/spec/lib/claims_api/v1/poa_pdf_constructor/base_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'claims_api/v1/poa_pdf_constructor/base'
+require_relative '../../shared_pdf_constructor_base_examples_spec'
+
+describe ClaimsApi::V1::PoaPdfConstructor::Base do
+ subject(:pdf_constructor_instance) { described_class.new }
+
+ describe '#set_limitation_of_consent_check_box' do
+ context 'marking the checkboxes correctly' do
+ it_behaves_like 'shared pdf constructor base behavior'
+ end
+ end
+end
diff --git a/modules/claims_api/spec/lib/claims_api/v2/poa_pdf_constructor/base_spec.rb b/modules/claims_api/spec/lib/claims_api/v2/poa_pdf_constructor/base_spec.rb
new file mode 100644
index 00000000000..0a98987a2e3
--- /dev/null
+++ b/modules/claims_api/spec/lib/claims_api/v2/poa_pdf_constructor/base_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'claims_api/v2/poa_pdf_constructor/base'
+require_relative '../../shared_pdf_constructor_base_examples_spec'
+
+describe ClaimsApi::V2::PoaPdfConstructor::Base do
+ subject(:pdf_constructor_instance) { described_class.new }
+
+ describe '#set_limitation_of_consent_check_box' do
+ context 'marking the checkboxes correctly' do
+ it_behaves_like 'shared pdf constructor base behavior'
+ end
+ end
+end
diff --git a/modules/claims_api/spec/models/veteran/service/user_spec.rb b/modules/claims_api/spec/models/veteran/service/user_spec.rb
index 4e11bc8f0f8..5047b220f72 100644
--- a/modules/claims_api/spec/models/veteran/service/user_spec.rb
+++ b/modules/claims_api/spec/models/veteran/service/user_spec.rb
@@ -16,10 +16,11 @@
end
let(:ows) { ClaimsApi::LocalBGS }
+ let(:org_web_service) { ClaimsApi::OrgWebService }
it 'initializes from a user' do
VCR.use_cassette('claims_api/bgs/claimant_web_service/find_poa_by_participant_id') do
- allow_any_instance_of(ows).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: { person_poa: [{ begin_dt: Time.zone.now, legacy_poa_cd: '033' }] } })
veteran = Veteran::User.new(user)
expect(veteran.power_of_attorney.code).to eq('074')
@@ -29,7 +30,7 @@
it 'does not bomb out if poa is missing' do
VCR.use_cassette('claims_api/bgs/claimant_web_service/not_find_poa_by_participant_id') do
- allow_any_instance_of(ows).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
veteran = Veteran::User.new(user)
expect(veteran.power_of_attorney).to eq(nil)
@@ -39,7 +40,7 @@
it 'provides most recent previous poa' do
VCR.use_cassette('claims_api/bgs/claimant_web_service/find_poa_by_participant_id') do
- allow_any_instance_of(ows).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({
person_poa_history: {
person_poa: [
@@ -56,7 +57,7 @@
it 'does not bomb out if poa history contains a single record' do
VCR.use_cassette('claims_api/bgs/claimant_web_service/find_poa_by_participant_id') do
- allow_any_instance_of(ows).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: { person_poa: { begin_dt: Time.zone.now, legacy_poa_cd: '033' } } })
veteran = Veteran::User.new(user)
expect(veteran.power_of_attorney.code).to eq('074')
diff --git a/modules/claims_api/spec/requests/metadata_spec.rb b/modules/claims_api/spec/requests/metadata_spec.rb
index 7da077c3b9f..8f89bbcec98 100644
--- a/modules/claims_api/spec/requests/metadata_spec.rb
+++ b/modules/claims_api/spec/requests/metadata_spec.rb
@@ -3,9 +3,7 @@
require 'rails_helper'
require 'bgs/services'
require 'mpi/service'
-require 'bgs_service/e_benefits_bnft_claim_status_web_service'
-require 'bgs_service/intent_to_file_web_service'
-require 'bgs_service/person_web_service'
+require 'bgs_service/local_bgs'
RSpec.describe 'ClaimsApi::Metadata', type: :request do
describe '#get /metadata' do
@@ -57,62 +55,131 @@
expect(result['mpi']['success']).to eq(false)
end
- local_bgs_services = %i[claimant org trackeditem].freeze
- local_bgs_methods = %i[find_poa_by_participant_id find_poa_history_by_ptcpnt_id
- find_tracked_items].freeze
- local_bgs_services.each do |local_bgs_service|
- it "returns the correct status when the local bgs #{local_bgs_service} is not healthy" do
- local_bgs_methods.each do |local_bgs_method|
- allow_any_instance_of(ClaimsApi::LocalBGS).to receive(local_bgs_method.to_sym)
- .and_return(Struct.new(:healthy?).new(false))
- get "/services/claims/#{version}/upstream_healthcheck"
- result = JSON.parse(response.body)
- expect(result["localbgs-#{local_bgs_service}"]['success']).to eq(false)
- end
- end
- end
-
- local_bgs_claims_status_services = %i[ebenefitsbenftclaim]
- local_bgs_claims_status_methods = %i[find_benefit_claims_status_by_ptcpnt_id]
- local_bgs_claims_status_services.each do |local_bgs_claims_status_service|
- it "returns the correct status when the local bgs #{local_bgs_claims_status_service} is not healthy" do
- local_bgs_claims_status_methods.each do |local_bgs_claims_status_method|
- allow_any_instance_of(ClaimsApi::EbenefitsBnftClaimStatusWebService).to receive(
- local_bgs_claims_status_method.to_sym
- )
- .and_return(Struct.new(:healthy?).new(false))
- get "/services/claims/#{version}/upstream_healthcheck"
- result = JSON.parse(response.body)
- expect(result["localbgs-#{local_bgs_claims_status_service}"]['success']).to eq(false)
- end
- end
- end
-
- local_bgs_itf_methods = %i[insert_intent_to_file]
- it 'returns the correct status when the local bgs intenttofile is not healthy' do
- local_bgs_itf_methods.each do |local_bgs_itf_method|
- allow_any_instance_of(ClaimsApi::IntentToFileWebService).to receive(
- local_bgs_itf_method.to_sym
- )
- .and_return(Struct.new(:healthy?).new(false))
- get "/services/claims/#{version}/upstream_healthcheck"
- result = JSON.parse(response.body)
- expect(result['localbgs-intenttofile']['success']).to eq(false)
- end
- end
-
- person_web_service = 'person'
- local_bgs_person_methods = %i[find_by_ssn]
- it "returns the correct status when the local bgs #{person_web_service} is not healthy" do
- local_bgs_person_methods.each do |local_bgs_person_method|
- allow_any_instance_of(ClaimsApi::PersonWebService).to receive(
- local_bgs_person_method.to_sym
- )
- .and_return(Struct.new(:healthy?).new(false))
- get "/services/claims/#{version}/upstream_healthcheck"
- result = JSON.parse(response.body)
- expect(result["localbgs-#{person_web_service}"]['success']).to eq(false)
- end
+ it 'returns the correct status when the benefit-claim-service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['benefit_claim_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the e-benefits-bnft-claim-status-web-service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['e_benefits_bnft_claim_status_web_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the claimant service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['claimant_web_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the claim_management_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['claim_management_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the contention_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['contention_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the corporate_update_web_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['corporate_update_web_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the intenttofile is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['intent_to_file_web_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the manage rep service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['manage_representative_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when bgs org service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['org_web_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the person_web_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['person_web_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the bgs standard_data_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['standard_data_web_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the bgs trackeditem is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['tracked_item_service']['success']).to eq(false)
+ end
+
+ it 'returns the correct status when the bgs vet_record service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['vet_record_web_service']['success']).to eq(false)
+ end
+
+ # 'bgs_service/vnp_atchms_service'
+ it 'returns the correct status when the bgs vnp_atchms_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['vnp_atchms_service']['success']).to eq(false)
+ end
+
+ # 'bgs_service/vnp_person_service'
+ it 'returns the correct status when the bgs vnp_person_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['vnp_person_service']['success']).to eq(false)
+ end
+
+ # 'bgs_service/vnp_proc_form_service'
+ it 'returns the correct status when the bgs vnp_proc_form_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['vnp_proc_form_service']['success']).to eq(false)
+ end
+
+ # 'bgs_service/vnp_proc_service_v2'
+ it 'returns the correct status when the bgs vnp_proc_service_v2 is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['vnp_proc_service_v2']['success']).to eq(false)
+ end
+
+ # 'bgs_service/vnp_ptcpnt_addrs_service'
+ it 'returns the correct status when the bgs vnp_ptcpnt_addrs_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['vnp_ptcpnt_addrs_service']['success']).to eq(false)
+ end
+
+ # 'bgs_service/vnp_ptcpnt_phone_service'
+ it 'returns the correct status when the bgs vnp_ptcpnt_phone_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['vnp_ptcpnt_phone_service']['success']).to eq(false)
+ end
+
+ # 'bgs_service/vnp_ptcpnt_service'
+ it 'returns the correct status when the bgs vnp_ptcpnt_service is not healthy' do
+ get "/services/claims/#{version}/upstream_healthcheck"
+ result = JSON.parse(response.body)
+ expect(result['vnp_ptcpnt_service']['success']).to eq(false)
end
end
end
diff --git a/modules/claims_api/spec/requests/v1/forms/526_spec.rb b/modules/claims_api/spec/requests/v1/forms/526_spec.rb
index e87fde5440d..86c94499f4a 100644
--- a/modules/claims_api/spec/requests/v1/forms/526_spec.rb
+++ b/modules/claims_api/spec/requests/v1/forms/526_spec.rb
@@ -267,7 +267,7 @@
end
end
- context "when 'treatments[].center.country' is too long'" do
+ context "when 'treatments[].center.country' is too long" do
let(:treated_disability_names) { ['PTSD (post traumatic stress disorder)'] }
it 'returns a bad request' do
@@ -1286,7 +1286,7 @@ def obj.class
end
context 'when consumer is representative' do
- it 'returns an unprocessible entity status' do
+ it 'returns an unprocessable entity status' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
post path, params: data, headers: headers.merge(auth_header)
@@ -1391,7 +1391,7 @@ def obj.class
stub_mpi(build(:mpi_profile, birls_id: nil, birth_date: '19560506'))
end
- it 'returns an unprocessible entity status' do
+ it 'returns an unprocessable entity status' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
post path, params: data, headers: headers.merge(auth_header)
@@ -1407,7 +1407,7 @@ def obj.class
stub_mpi(build(:mpi_profile, birls_id: nil, birth_date: '19560506'))
end
- it 'returns an unprocessible entity status' do
+ it 'returns an unprocessable entity status' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
VCR.use_cassette('claims_api/bgs/claims/claims') do
@@ -1992,7 +1992,7 @@ def obj.class
context "when 'amount' is below the minimum" do
let(:military_retired_payment_amount) { 0 }
- it 'responds with an unprocessible entity' do
+ it 'responds with an unprocessable entity' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
json_data = JSON.parse data
@@ -2008,7 +2008,7 @@ def obj.class
context "when 'amount' is above the maximum" do
let(:military_retired_payment_amount) { 1_000_000 }
- it 'responds with an unprocessible entity' do
+ it 'responds with an unprocessable entity' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/bgs/claims/claims') do
VCR.use_cassette('claims_api/brd/countries') do
@@ -2059,7 +2059,7 @@ def obj.class
}
end
- it 'responds with an unprocessible entity' do
+ it 'responds with an unprocessable entity' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
json_data = JSON.parse data
@@ -2122,7 +2122,7 @@ def obj.class
context "when 'amount' is below the minimum" do
let(:separation_payment_amount) { 0 }
- it 'responds with an unprocessible entity' do
+ it 'responds with an unprocessable entity' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
json_data = JSON.parse data
@@ -2138,7 +2138,7 @@ def obj.class
context "when 'amount' is above the maximum" do
let(:separation_payment_amount) { 1_000_000 }
- it 'responds with an unprocessible entity' do
+ it 'responds with an unprocessable entity' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/bgs/claims/claims') do
VCR.use_cassette('claims_api/brd/countries') do
@@ -2272,7 +2272,7 @@ def obj.class
end
end
- context "when 'disabilites.secondaryDisabilities.classificationCode' is invalid" do
+ context "when 'disabilities.secondaryDisabilities.classificationCode' is invalid" do
let(:classification_type_codes) { [{ clsfcn_id: '1111' }] }
[true, false].each do |flipped|
@@ -2318,7 +2318,7 @@ def obj.class
end
end
- context "when 'disabilites.secondaryDisabilities.classificationCode' does not match name" do
+ context "when 'disabilities.secondaryDisabilities.classificationCode' does not match name" do
let(:classification_type_codes) { [{ clsfcn_id: '1111' }] }
[true, false].each do |flipped|
@@ -2364,7 +2364,7 @@ def obj.class
end
end
- context "when 'disabilites.secondaryDisabilities.approximateBeginDate' is present" do
+ context "when 'disabilities.secondaryDisabilities.approximateBeginDate' is present" do
it 'raises an exception if date is invalid' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
@@ -2420,7 +2420,7 @@ def obj.class
end
end
- context "when 'disabilites.secondaryDisabilities.classificationCode' is not present" do
+ context "when 'disabilities.secondaryDisabilities.classificationCode' is not present" do
it 'raises an exception if name is not valid structure' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
@@ -2477,24 +2477,24 @@ def obj.class
end
end
- describe "'disabilites' validations" do
+ describe "'disabilities' validations" do
describe "'disabilities.classificationCode' validations" do
[true, false].each do |flipped|
context "when feature flag is #{flipped}" do
before do
allow(Flipper).to receive(:enabled?).with(:claims_api_526_validations_v1_local_bgs).and_return(flipped)
if flipped
- expect_any_instance_of(ClaimsApi::StandardDataService)
+ allow_any_instance_of(ClaimsApi::StandardDataService)
.to receive(:get_contention_classification_type_code_list).and_return(classification_type_codes)
else
- expect_any_instance_of(BGS::StandardDataService)
+ allow_any_instance_of(BGS::StandardDataService)
.to receive(:get_contention_classification_type_code_list).and_return(classification_type_codes)
end
end
- let(:classification_type_codes) { [{ clsfcn_id: '1111' }] }
+ let(:classification_type_codes) { [{ clsfcn_id: '1111', end_dt: 1.year.from_now.iso8601 }] }
- context "when 'disabilites.classificationCode' is valid" do
+ context "when 'disabilities.classificationCode' is valid and expires in the future" do
it 'returns a successful response' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/bgs/claims/claims') do
@@ -2517,11 +2517,52 @@ def obj.class
end
end
- context "when 'disabilites.classificationCode' is invalid" do
+ context "when 'disabilities.classificationCode' is valid but expires in the past" do
+ before do
+ if Flipper.enabled?(:claims_api_526_validations_v1_local_bgs)
+ allow_any_instance_of(ClaimsApi::StandardDataService)
+ .to receive(:get_contention_classification_type_code_list)
+ .and_return([{
+ clsfcn_id: '1111',
+ end_dt: 1.year.ago.iso8601
+ }])
+ else
+ allow_any_instance_of(BGS::StandardDataService)
+ .to receive(:get_contention_classification_type_code_list)
+ .and_return([{
+ clsfcn_id: '1111',
+ end_dt: 1.year.ago.iso8601
+ }])
+ end
+ end
+
+ it 'responds with a bad request' do
+ mock_acg(scopes) do |auth_header|
+ VCR.use_cassette('claims_api_bgs/claims/claims') do
+ VCR.use_cassette('claims_api/brd/countries') do
+ json_data = JSON.parse data
+ params = json_data
+ disabilities = [
+ {
+ disabilityActionType: 'NEW',
+ name: 'PTSD (post traumatic stress disorder)',
+ classificationCode: '1111'
+ }
+ ]
+ params['data']['attributes']['disabilities'] = disabilities
+ post path, params: params.to_json, headers: headers.merge(auth_header)
+ expect(response).to have_http_status(:bad_request)
+ end
+ end
+ end
+ end
+ end
+
+ context "when 'disabilities.classificationCode' is invalid" do
it 'responds with a bad request' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
- VCR.use_cassette('claims_api/bgs/stadard_service_data') do
+ VCR.use_cassette('claims_api/bgs/standard_service_data') do
json_data = JSON.parse data
params = json_data
disabilities = [
@@ -2544,9 +2585,9 @@ def obj.class
end
describe "'disabilities.ratedDisabilityId' validations" do
- context "when 'disabilites.disabilityActionType' equals 'INCREASE'" do
+ context "when 'disabilities.disabilityActionType' equals 'INCREASE'" do
context "and 'disabilities.ratedDisabilityId' is not provided" do
- it 'returns an unprocessible entity status' do
+ it 'returns an unprocessable entity status' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/bgs/claims/claims') do
VCR.use_cassette('claims_api/brd/countries') do
@@ -2593,7 +2634,7 @@ def obj.class
end
context "and 'disabilities.diagnosticCode' is not provided" do
- it 'returns an unprocessible entity status' do
+ it 'returns an unprocessable entity status' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/brd/countries') do
json_data = JSON.parse data
@@ -2614,10 +2655,10 @@ def obj.class
end
end
- context "when 'disabilites.disabilityActionType' equals 'NONE'" do
- context "and 'disabilites.secondaryDisabilities' is defined" do
- context "and 'disabilites.diagnosticCode is not provided" do
- it 'returns an unprocessible entity status' do
+ context "when 'disabilities.disabilityActionType' equals 'NONE'" do
+ context "and 'disabilities.secondaryDisabilities' is defined" do
+ context "and 'disabilities.diagnosticCode is not provided" do
+ it 'returns an unprocessable entity status' do
mock_acg(scopes) do |auth_header|
VCR.use_cassette('claims_api/bgs/claims/claims') do
VCR.use_cassette('claims_api/brd/countries') do
@@ -2648,7 +2689,7 @@ def obj.class
end
end
- context "when 'disabilites.disabilityActionType' equals value other than 'INCREASE'" do
+ context "when 'disabilities.disabilityActionType' equals value other than 'INCREASE'" do
context "and 'disabilities.ratedDisabilityId' is not provided" do
it 'responds with a 200' do
mock_acg(scopes) do |auth_header|
@@ -2674,7 +2715,7 @@ def obj.class
end
end
- describe "'disabilites.approximateBeginDate' validations" do
+ describe "'disabilities.approximateBeginDate' validations" do
let(:disabilities) do
[
{
@@ -2727,7 +2768,7 @@ def obj.class
end
end
- describe "'disabilites.specialIssues' validations" do
+ describe "'disabilities.specialIssues' validations" do
let(:disabilities) do
[
{
@@ -2882,7 +2923,7 @@ def obj.class
end
end
- context "when 'specialIssues' are provided for some 'disabilites'" do
+ context "when 'specialIssues' are provided for some 'disabilities'" do
let(:disabilities) do
[
{
diff --git a/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/request_spec.rb b/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/request_spec.rb
index aaa53a2e8f2..db1b9e4ce71 100644
--- a/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/request_spec.rb
+++ b/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/request_spec.rb
@@ -3,7 +3,7 @@
require 'rails_helper'
require Rails.root / 'modules/claims_api/spec/rails_helper'
-RSpec.describe 'ClaimsApi::V2::PowerOfAttorneyRequests#index', :bgs, type: :request do
+RSpec.describe 'ClaimsApi::V2::PowerOfAttorneyRequests#index', :bgs, skip: 'unused', type: :request do
subject { perform_request(params) }
def perform_request(params)
diff --git a/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/rswag/200.json b/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/rswag/200.json
index 173900bfe33..2ae3cffdabe 100644
--- a/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/rswag/200.json
+++ b/modules/claims_api/spec/requests/v2/power_of_attorney_requests/index/rswag/200.json
@@ -1,121 +1,104 @@
{
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "VSOUserEmail": {
- "type": [
- "string",
- "null"
- ]
- },
- "VSOUserFirstName": {
- "type": [
- "string",
- "null"
- ]
- },
- "VSOUserLastName": {
- "type": [
- "string",
- "null"
- ]
- },
- "changeAddressAuth": {
- "type": [
- "string",
- "null"
- ]
- },
- "claimantCity": {
- "type": [
- "string",
- "null"
- ]
- },
- "claimantCountry": {
- "type": [
- "string",
- "null"
- ]
- },
- "claimantMilitaryPO": {
- "type": [
- "string",
- "null"
- ]
- },
- "claimantMilitaryPostalCode": {
- "type": [
- "string",
- "null"
- ]
- },
- "claimantState": {
- "type": [
- "string",
- "null"
- ]
- },
- "claimantZip": {
- "type": [
- "string",
- "null"
- ]
- },
- "dateRequestActioned": {
- "type": "string"
- },
- "dateRequestReceived": {
- "type": "string"
- },
- "declinedReason": {
- "type": [
- "string",
- "null"
- ]
- },
- "healthInfoAuth": {
- "type": [
- "string",
- "null"
- ]
- },
- "poaCode": {
- "type": [
- "string",
- "null"
- ]
- },
- "procID": {
- "type": "string"
- },
- "secondaryStatus": {
- "type": "string"
- },
- "vetFirstName": {
- "type": [
- "string",
- "null"
- ]
- },
- "vetLastName": {
- "type": [
- "string",
- "null"
- ]
- },
- "vetMiddleName": {
- "type": [
- "string",
- "null"
- ]
- },
- "vetPtcpntID": {
- "type": [
- "string",
- "null"
- ]
+ "type": "object",
+ "required": ["data"],
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["type", "attributes"],
+ "properties": {
+ "id": {
+ "type": ["string", "null"],
+ "format": "uuid"
+ },
+ "type": {
+ "type": "string",
+ "enum": ["power-of-attorney-request"]
+ },
+ "attributes": {
+ "type": "object",
+ "required": ["veteran", "claimant", "representative"],
+ "properties": {
+ "veteran": {
+ "type": "object",
+ "properties": {
+ "firstName": {
+ "type": ["string", "null"]
+ },
+ "lastName": {
+ "type": ["string", "null"]
+ },
+ "middleName": {
+ "type": ["string", "null"]
+ }
+ }
+ },
+ "claimant": {
+ "type": "object",
+ "properties": {
+ "city": {
+ "type": ["string", "null"]
+ },
+ "country": {
+ "type": ["string", "null"]
+ },
+ "militaryPo": {
+ "type": ["string", "null"]
+ },
+ "militaryPostalCode": {
+ "type": ["string", "null"]
+ },
+ "state": {
+ "type": ["string", "null"]
+ },
+ "zip": {
+ "type": ["string", "null"]
+ }
+ }
+ },
+ "representative": {
+ "type": "object",
+ "properties": {
+ "poaCode": {
+ "type": ["string", "null"]
+ },
+ "vsoUserEmail": {
+ "type": ["string", "null"]
+ },
+ "vsoUserFirstName": {
+ "type": ["string", "null"]
+ },
+ "vsoUserLastName": {
+ "type": ["string", "null"]
+ }
+ }
+ },
+ "receivedDate": {
+ "type": ["string", "null"],
+ "format": "date-time"
+ },
+ "actionedDate": {
+ "type": ["string", "null"],
+ "format": "date-time"
+ },
+ "status": {
+ "type": ["string", "null"]
+ },
+ "declinedReason": {
+ "type": ["string", "null"]
+ },
+ "changeAddressAuthorization": {
+ "type": ["string", "null"],
+ "enum": ["Y", "N", null]
+ },
+ "healthInfoAuthorization": {
+ "type": ["string", "null"],
+ "enum": ["Y", "N", null]
+ }
+ }
+ }
+ }
}
}
}
diff --git a/modules/claims_api/spec/requests/v2/veterans/claims_spec.rb b/modules/claims_api/spec/requests/v2/veterans/claims_spec.rb
index 403fd5ab6ac..0f66a800c91 100644
--- a/modules/claims_api/spec/requests/v2/veterans/claims_spec.rb
+++ b/modules/claims_api/spec/requests/v2/veterans/claims_spec.rb
@@ -6,6 +6,7 @@
require 'bgs_service/local_bgs'
require 'bgs_service/person_web_service'
require 'bgs_service/e_benefits_bnft_claim_status_web_service'
+require 'bgs_service/tracked_item_service'
require 'concerns/claims_api/v2/claims_requests/supporting_documents'
RSpec.describe 'ClaimsApi::V2::Veterans::Claims', type: :request do
diff --git a/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/2122_spec.rb b/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/2122_spec.rb
index 4b0acb347da..2df13865dc5 100644
--- a/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/2122_spec.rb
+++ b/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/2122_spec.rb
@@ -4,6 +4,7 @@
require_relative '../../../../rails_helper'
require 'token_validation/v2/client'
require 'bgs_service/local_bgs'
+require 'bgs_service/org_web_service'
RSpec.describe 'ClaimsApi::V1::PowerOfAttorney::2122', type: :request do
let(:veteran_id) { '1013062086V794840' }
@@ -14,6 +15,7 @@
let(:organization_poa_code) { '083' }
let(:bgs_poa) { { person_org_name: "#{individual_poa_code} name-here" } }
let(:local_bgs) { ClaimsApi::LocalBGS }
+ let(:org_web_service) { ClaimsApi::OrgWebService }
describe 'PowerOfAttorney' do
before do
@@ -55,7 +57,7 @@
mock_ccg(scopes) do |auth_header|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id)
.and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
post appoint_organization_path, params: data.to_json, headers: auth_header
@@ -86,7 +88,7 @@
it 'returns a 422 when no zipCode is included in the veteran data' do
VCR.use_cassette('claims_api/mpi/find_candidate/valid_icn_full') do
mock_ccg(scopes) do |auth_header|
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
json = JSON.parse(request_body)
@@ -112,7 +114,7 @@
mock_ccg(scopes) do |auth_header|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id)
.and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
json = JSON.parse(request_body)
@@ -284,7 +286,7 @@
mock_ccg(scopes) do |auth_header|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id)
.and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
allow_any_instance_of(ClaimsApi::V2::ApplicationController)
.to receive(:target_veteran).and_return(no_first_name_target_veteran)
@@ -316,7 +318,7 @@
it 'returns a 422' do
mock_ccg(scopes) do |auth_header|
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
allow_any_instance_of(ClaimsApi::V2::ApplicationController)
.to receive(:target_veteran).and_return(no_first_last_name_target_veteran)
@@ -356,7 +358,7 @@
it 'returns a 422 with no zipCode' do
VCR.use_cassette('claims_api/mpi/find_candidate/valid_icn_full') do
mock_ccg(scopes) do |auth_header|
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
json = JSON.parse(request_body)
@@ -395,7 +397,7 @@
mock_ccg(scopes) do |auth_header|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id)
.and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
json = JSON.parse(request_body)
@@ -418,7 +420,7 @@
mock_ccg(scopes) do |auth_header|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id)
.and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
post appoint_organization_path, params: data.to_json, headers: auth_header
@@ -432,7 +434,7 @@
mock_ccg(scopes) do |auth_header|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id)
.and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
post appoint_organization_path, params: data.to_json, headers: auth_header
@@ -490,7 +492,7 @@
mock_ccg_for_fine_grained_scope(poa_scopes) do |auth_header|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id)
.and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
post appoint_organization_path, params: data.to_json, headers: auth_header
@@ -519,7 +521,7 @@
mock_ccg(scopes) do |auth_header|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id)
.and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
expect(ClaimsApi::V2::PoaFormBuilderJob).to receive(:perform_async) do |*args|
expect(args[2]).to eq(rep_id)
@@ -605,7 +607,7 @@
it 'returns an error response' do
mock_ccg(scopes) do |auth_header|
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
allow_any_instance_of(ClaimsApi::V2::ApplicationController)
.to receive(:target_veteran).and_return(no_first_last_name_target_veteran)
diff --git a/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/2122a_spec.rb b/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/2122a_spec.rb
index e64ad9544ae..93cf18b69f0 100644
--- a/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/2122a_spec.rb
+++ b/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/2122a_spec.rb
@@ -4,6 +4,7 @@
require_relative '../../../../rails_helper'
require 'token_validation/v2/client'
require 'bgs_service/local_bgs'
+require 'bgs_service/org_web_service'
RSpec.describe 'ClaimsApi::V2::PowerOfAttorney::2122a', type: :request do
let(:veteran_id) { '1013062086V794840' }
@@ -14,6 +15,7 @@
let(:organization_poa_code) { '067' }
let(:bgs_poa) { { person_org_name: "#{individual_poa_code} name-here" } }
let(:local_bgs) { ClaimsApi::LocalBGS }
+ let(:org_web_service) { ClaimsApi::OrgWebService }
describe 'PowerOfAttorney' do
before do
@@ -113,7 +115,7 @@
mock_ccg(scopes) do |auth_header|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id)
.and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
post appoint_individual_path, params: data.to_json, headers: auth_header
@@ -312,7 +314,7 @@
claimant_data[:data][:attributes][:representative][:address][:zipCode] = ''
claimant_data[:data][:attributes][:representative][:address][:countryCode] = 'AL'
allow_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id).and_return(bgs_poa)
- allow_any_instance_of(local_bgs)
+ allow_any_instance_of(org_web_service)
.to receive(:find_poa_history_by_ptcpnt_id).and_return({ person_poa_history: nil })
VCR.use_cassette('claims_api/mpi/find_candidate/valid_icn_full') do
post appoint_individual_path, params: claimant_data.to_json, headers: auth_header
@@ -327,7 +329,7 @@
shared_context 'claimant data setup' do
before do
allow_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id).and_return(bgs_poa)
- allow_any_instance_of(local_bgs)
+ allow_any_instance_of(org_web_service)
.to receive(:find_poa_history_by_ptcpnt_id).and_return({ person_poa_history: nil })
end
end
diff --git a/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/power_of_attorney_request_spec.rb b/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/power_of_attorney_request_spec.rb
index 5fb9c40f831..f8fb7db325c 100644
--- a/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/power_of_attorney_request_spec.rb
+++ b/modules/claims_api/spec/requests/v2/veterans/power_of_attorney/power_of_attorney_request_spec.rb
@@ -162,9 +162,13 @@
post request_path, params: request_body, headers: auth_header
response_body = JSON.parse(response.body)
+ request_body_with_type_and_id = JSON.parse(request_body)
+
+ request_body_with_type_and_id['data']['type'] = 'power-of-attorney-request'
+ request_body_with_type_and_id['data']['id'] = response_body['data']['id']
expect(response).to have_http_status(:created)
- expect(response_body).to eq(JSON.parse(request_body))
+ expect(response_body).to eq(request_body_with_type_and_id)
end
end
end
diff --git a/modules/claims_api/spec/requests/v2/veterans/rswag_claims_spec.rb b/modules/claims_api/spec/requests/v2/veterans/rswag_claims_spec.rb
index 591285ded3d..017343e7f77 100644
--- a/modules/claims_api/spec/requests/v2/veterans/rswag_claims_spec.rb
+++ b/modules/claims_api/spec/requests/v2/veterans/rswag_claims_spec.rb
@@ -4,6 +4,8 @@
require 'rails_helper'
require_relative '../../../rails_helper'
require 'bgs_service/local_bgs'
+require 'bgs_service/tracked_item_service'
+require 'bgs_service/e_benefits_bnft_claim_status_web_service'
describe 'Claims',
openapi_spec: Rswag::TextHelpers.new.claims_api_docs do
diff --git a/modules/claims_api/spec/requests/v2/veterans/rswag_power_of_attorney_spec.rb b/modules/claims_api/spec/requests/v2/veterans/rswag_power_of_attorney_spec.rb
index 0f6e8117e31..e476387aa64 100644
--- a/modules/claims_api/spec/requests/v2/veterans/rswag_power_of_attorney_spec.rb
+++ b/modules/claims_api/spec/requests/v2/veterans/rswag_power_of_attorney_spec.rb
@@ -6,10 +6,12 @@
require_relative '../../../rails_helper'
require_relative '../../../support/swagger_shared_components/v2'
require 'bgs_service/local_bgs'
+require 'bgs_service/org_web_service'
describe 'PowerOfAttorney',
openapi_spec: Rswag::TextHelpers.new.claims_api_docs do
let(:local_bgs) { ClaimsApi::LocalBGS }
+ let(:org_web_service) { ClaimsApi::OrgWebService }
claimant_data = {
'claimantId' => '1013093331V548481',
@@ -61,7 +63,7 @@
before do |example|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id).and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
FactoryBot.create(:veteran_representative, representative_id: '12345',
poa_codes: [poa_code],
@@ -119,7 +121,7 @@
before do |example|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id).and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
FactoryBot.create(:veteran_representative, representative_id: '12345',
@@ -153,7 +155,7 @@
before do |example|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id).and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
FactoryBot.create(:veteran_representative, representative_id: '12345',
poa_codes: [poa_code],
@@ -262,7 +264,7 @@
before do |example|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id).and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
FactoryBot.create(:veteran_representative, representative_id: '999999999999',
poa_codes: [poa_code],
@@ -459,7 +461,7 @@
before do |example|
expect_any_instance_of(local_bgs).to receive(:find_poa_by_participant_id).and_return(bgs_poa)
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
FactoryBot.create(:veteran_organization, poa: organization_poa_code,
name: "#{organization_poa_code} - DISABLED AMERICAN VETERANS",
@@ -528,7 +530,7 @@
before do |example|
mock_ccg(scopes) do
- allow_any_instance_of(local_bgs).to receive(:find_poa_history_by_ptcpnt_id)
+ allow_any_instance_of(org_web_service).to receive(:find_poa_history_by_ptcpnt_id)
.and_return({ person_poa_history: nil })
submit_request(example.metadata)
end
diff --git a/modules/claims_api/spec/sidekiq/poa_assign_dependent_claimant_job_spec.rb b/modules/claims_api/spec/sidekiq/poa_assign_dependent_claimant_job_spec.rb
index 369967b3b4f..7bc9b6e9ba3 100644
--- a/modules/claims_api/spec/sidekiq/poa_assign_dependent_claimant_job_spec.rb
+++ b/modules/claims_api/spec/sidekiq/poa_assign_dependent_claimant_job_spec.rb
@@ -68,10 +68,61 @@
)
expect(poa.status).to eq(ClaimsApi::PowerOfAttorney::SUBMITTED)
- described_class.new.perform(poa.id)
+ described_class.new.perform(poa.id, 'Rep Data')
poa.reload
expect(poa.status).to eq(ClaimsApi::PowerOfAttorney::UPDATED)
end
end
+
+ context 'Sending the VA Notify email' do
+ before do
+ create_mock_lighthouse_service
+ allow(Flipper).to receive(:enabled?).with(:lighthouse_claims_api_v2_poa_va_notify).and_return true
+ end
+
+ let(:poa) do
+ FactoryBot.create(:power_of_attorney,
+ auth_headers: auth_headers,
+ form_data: claimant_form_data,
+ status: ClaimsApi::PowerOfAttorney::SUBMITTED)
+ end
+ let(:header_key) { ClaimsApi::V2::Veterans::PowerOfAttorney::BaseController::VA_NOTIFY_KEY }
+
+ context 'when the header key and rep are present' do
+ it 'sends the vanotify job' do
+ poa.auth_headers.merge!({
+ header_key => 'this_value'
+ })
+ poa.save!
+ allow_any_instance_of(ClaimsApi::DependentClaimantPoaAssignmentService).to receive(:assign_poa_to_dependent!)
+ .and_return(nil)
+ allow_any_instance_of(ClaimsApi::ServiceBase).to receive(:vanotify?).and_return true
+ expect(ClaimsApi::VANotifyAcceptedJob).to receive(:perform_async)
+
+ described_class.new.perform(poa.id, '12345678')
+ end
+ end
+
+ context 'when the flipper is off' do
+ it 'does not send the vanotify job' do
+ allow(Flipper).to receive(:enabled?).with(:lighthouse_claims_api_v2_poa_va_notify).and_return false
+ poa.auth_headers.merge!({
+ header_key => 'this_value'
+ })
+ poa.save!
+ allow_any_instance_of(ClaimsApi::DependentClaimantPoaAssignmentService).to receive(:assign_poa_to_dependent!)
+ .and_return(nil)
+ expect(ClaimsApi::VANotifyAcceptedJob).not_to receive(:perform_async)
+
+ described_class.new.perform(poa.id, '12345678')
+ end
+ end
+ end
+
+ def create_mock_lighthouse_service
+ allow_any_instance_of(ClaimsApi::StandardDataWebService).to receive(:find_poas)
+ .and_return([{ legacy_poa_cd: '002', nm: "MAINE VETERANS' SERVICES", org_type_nm: 'POA State Organization',
+ ptcpnt_id: '46004' }])
+ end
end
diff --git a/modules/claims_api/spec/sidekiq/poa_updater_spec.rb b/modules/claims_api/spec/sidekiq/poa_updater_spec.rb
index 877c8042720..0c5fd10aac9 100644
--- a/modules/claims_api/spec/sidekiq/poa_updater_spec.rb
+++ b/modules/claims_api/spec/sidekiq/poa_updater_spec.rb
@@ -99,13 +99,14 @@
})
poa.save!
+ allow_any_instance_of(ClaimsApi::ServiceBase).to receive(:vanotify?).and_return true
expect(ClaimsApi::VANotifyAcceptedJob).to receive(:perform_async)
subject.new.perform(poa.id, 'Rep Data')
end
end
- context 'when the flipper is on' do
+ context 'when the flipper is off' do
it 'does not send the vanotify job' do
allow(Flipper).to receive(:enabled?).with(:lighthouse_claims_api_v2_poa_va_notify).and_return false
Flipper.disable(:lighthouse_claims_api_v2_poa_va_notify)
diff --git a/modules/decision_reviews/README.md b/modules/decision_reviews/README.md
index 39e7f0e002e..8f993a33735 100644
--- a/modules/decision_reviews/README.md
+++ b/modules/decision_reviews/README.md
@@ -49,7 +49,7 @@ Models
Each of these categories includes tests
-### Phase 1: Duplicate Background Jobs and Primary Utilities
+### COMPLETE - Phase 1: Duplicate Background Jobs and Primary Utilities
Create copies of all our background jobs inside the engine, including tests. Create copies of any primary utilities in the engine, but allow them to reference secondary utilities that stay in the main application. Any model references should point to original models in the main application. Mount the engine in the main application (it should not do anything).
@@ -61,7 +61,7 @@ Mitigation:
- Code freeze, except for urgent bugs: Background jobs and Utilities
-### Phase 2: Transition to Engine Scheduled Background Jobs
+### COMPLETE - Phase 2: Transition to Engine Scheduled Background Jobs
Update the schedule file to ALSO reference the engine version of each job. All these jobs are idempotent (can be run multiple times with no overlapping effects) so this should be safe. Exclude any jobs that are not scheduled (form4142_submit and submit_upload), they are not safe to run multiple times.
@@ -87,7 +87,7 @@ Mitigation:
- Code freeze, except for urgent bugs: Background jobs and Utilities
-### Phase 4: Duplicate Controllers and Necessary Utilities
+### COMPLETE - Phase 4: Duplicate Controllers and Necessary Utilities
Duplicate all controllers, tests, and any primary utilities they reference. Allow them to reference secondary utilities that stay in the main application. Any model references should point to original models in the main application. Add routes to the engine that preserve the original routes but namespace them to the engine.
diff --git a/modules/decision_reviews/app/sidekiq/decision_reviews/failure_notification_email_job.rb b/modules/decision_reviews/app/sidekiq/decision_reviews/failure_notification_email_job.rb
index f5ef7618c54..0ff4b6fc8bc 100644
--- a/modules/decision_reviews/app/sidekiq/decision_reviews/failure_notification_email_job.rb
+++ b/modules/decision_reviews/app/sidekiq/decision_reviews/failure_notification_email_job.rb
@@ -146,10 +146,6 @@ def record_form_email_send_successful(submission, notification_id)
params = { submitted_appeal_uuid: submission.submitted_appeal_uuid, appeal_type:, notification_id: }
Rails.logger.info('DecisionReviews::FailureNotificationEmailJob form email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.form.email_queued", tags: ["appeal_type:#{appeal_type}"])
-
- tags = ["service:#{DecisionReviews::V1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: form submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
def record_form_email_send_failure(submission, e)
@@ -172,10 +168,6 @@ def record_secondary_form_email_send_successful(secondary_form, notification_id)
notification_id: }
Rails.logger.info('DecisionReviews::FailureNotificationEmailJob secondary form email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.secondary_form.email_queued", tags: ["appeal_type:#{appeal_type}"])
-
- tags = ["service:#{DecisionReviews::V1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: secondary form submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
def record_secondary_form_email_send_failure(secondary_form, e)
@@ -204,10 +196,6 @@ def record_evidence_email_send_successful(upload, notification_id)
}
Rails.logger.info('DecisionReviews::FailureNotificationEmailJob evidence email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.evidence.email_queued", tags: ["appeal_type:#{appeal_type}"])
-
- tags = ["service:#{DecisionReviews::V1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: evidence submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
def record_evidence_email_send_failure(upload, e)
diff --git a/modules/decision_reviews/app/sidekiq/decision_reviews/form4142_submit.rb b/modules/decision_reviews/app/sidekiq/decision_reviews/form4142_submit.rb
index 6427e2fbf20..049cd459fbe 100644
--- a/modules/decision_reviews/app/sidekiq/decision_reviews/form4142_submit.rb
+++ b/modules/decision_reviews/app/sidekiq/decision_reviews/form4142_submit.rb
@@ -19,9 +19,6 @@ class Form4142Submit
appeal_submission_id, _encrypted_payload, submitted_appeal_uuid = msg['args']
job_id = msg['jid']
- tags = ['service:supplemental-claims', 'function: secondary form submission to Lighthouse']
- StatsD.increment('silent_failure', tags:)
-
::Rails.logger.error(
{
error_message:,
@@ -99,10 +96,6 @@ def self.record_email_send_successful(submission, notification_id)
notification_id: }
Rails.logger.info('DecisionReviews::Form4142Submit retries exhausted email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.retries_exhausted.email_queued")
-
- tags = ["service:#{DecisionReviews::V1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: secondary form submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
private_class_method :record_email_send_successful
@@ -113,6 +106,9 @@ def self.record_email_send_failure(submission, e)
message: e.message }
Rails.logger.error('DecisionReviews::Form4142Submit retries exhausted email error', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.retries_exhausted.email_error", tags: ["appeal_type:#{appeal_type}"])
+
+ tags = ['service:supplemental-claims', 'function: secondary form submission to Lighthouse']
+ StatsD.increment('silent_failure', tags:)
end
private_class_method :record_email_send_failure
end
diff --git a/modules/decision_reviews/app/sidekiq/decision_reviews/submit_upload.rb b/modules/decision_reviews/app/sidekiq/decision_reviews/submit_upload.rb
index 5df33f76e9e..d30a7c2311e 100644
--- a/modules/decision_reviews/app/sidekiq/decision_reviews/submit_upload.rb
+++ b/modules/decision_reviews/app/sidekiq/decision_reviews/submit_upload.rb
@@ -22,10 +22,6 @@ class SubmitUpload
upload = AppealSubmissionUpload.find(appeal_submission_upload_id)
submission = upload.appeal_submission
- service_name = DecisionReviews::V1::APPEAL_TYPE_TO_SERVICE_MAP[submission.type_of_appeal]
- tags = ["service:#{service_name}", 'function: evidence submission to Lighthouse']
- StatsD.increment('silent_failure', tags:)
-
::Rails.logger.error({ error_message:, message:, appeal_submission_upload_id:, job_id: })
StatsD.increment("#{STATSD_KEY_PREFIX}.permanent_error")
@@ -189,10 +185,6 @@ def self.record_email_send_successful(upload, submission, notification_id)
notification_id: }
Rails.logger.info('DecisionReviews::SubmitUpload retries exhausted email queued', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.retries_exhausted.email_queued")
-
- tags = ["service:#{DecisionReviews::V1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]}",
- 'function: evidence submission to Lighthouse']
- StatsD.increment('silent_failure_avoided_no_confirmation', tags:)
end
private_class_method :record_email_send_successful
@@ -204,6 +196,10 @@ def self.record_email_send_failure(upload, submission, e)
message: e.message }
Rails.logger.error('DecisionReviews::SubmitUpload retries exhausted email error', params)
StatsD.increment("#{STATSD_KEY_PREFIX}.retries_exhausted.email_error", tags: ["appeal_type:#{appeal_type}"])
+
+ service_name = DecisionReviews::V1::APPEAL_TYPE_TO_SERVICE_MAP[appeal_type]
+ tags = ["service:#{service_name}", 'function: evidence submission to Lighthouse']
+ StatsD.increment('silent_failure', tags:)
end
private_class_method :record_email_send_failure
end
diff --git a/modules/decision_reviews/spec/controllers/decision_review_evidences_controller_spec.rb b/modules/decision_reviews/spec/controllers/decision_review_evidences_controller_spec.rb
index 238fdbfbdb0..664d8cffb19 100644
--- a/modules/decision_reviews/spec/controllers/decision_review_evidences_controller_spec.rb
+++ b/modules/decision_reviews/spec/controllers/decision_review_evidences_controller_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
-require 'rails_helper'
+require './modules/decision_reviews/spec/dr_spec_helper'
+require './modules/decision_reviews/spec/support/vcr_helper'
RSpec.describe DecisionReviews::V1::DecisionReviewEvidencesController, type: :controller do
routes { DecisionReviews::Engine.routes }
diff --git a/modules/decision_reviews/spec/controllers/decision_review_notification_callbacks_controller_spec.rb b/modules/decision_reviews/spec/controllers/decision_review_notification_callbacks_controller_spec.rb
index a0373d0682d..9529071341a 100644
--- a/modules/decision_reviews/spec/controllers/decision_review_notification_callbacks_controller_spec.rb
+++ b/modules/decision_reviews/spec/controllers/decision_review_notification_callbacks_controller_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require './modules/decision_reviews/spec/dr_spec_helper'
RSpec.describe DecisionReviews::V1::DecisionReviewNotificationCallbacksController, type: :controller do
routes { DecisionReviews::Engine.routes }
diff --git a/modules/decision_reviews/spec/dr_spec_helper.rb b/modules/decision_reviews/spec/dr_spec_helper.rb
index 72f4efcade9..31ba8e21a81 100644
--- a/modules/decision_reviews/spec/dr_spec_helper.rb
+++ b/modules/decision_reviews/spec/dr_spec_helper.rb
@@ -14,6 +14,7 @@
require 'support/stub_va_profile'
require 'support/mpi/stub_mpi'
require 'support/factory_bot'
+require 'support/authenticated_session_helper'
WebMock.disable_net_connect!(allow_localhost: true)
@@ -65,7 +66,15 @@ def with_settings(settings, temp_values)
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
+ ## authentication_session_helper
+ config.include AuthenticatedSessionHelper, type: :request
+ config.include AuthenticatedSessionHelper, type: :controller
+
config.include StatsD::Instrument::Matchers
+
+ config.before :each, type: :controller do
+ request.host = Settings.hostname
+ end
end
Gem::Deprecate.skip = true
diff --git a/modules/decision_reviews/spec/requests/v1/higher_level_reviews/contestable_issues_spec.rb b/modules/decision_reviews/spec/requests/v1/higher_level_reviews/contestable_issues_spec.rb
index 24796d0f20a..58782f2422c 100644
--- a/modules/decision_reviews/spec/requests/v1/higher_level_reviews/contestable_issues_spec.rb
+++ b/modules/decision_reviews/spec/requests/v1/higher_level_reviews/contestable_issues_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require 'rails_helper'
-require 'support/controller_spec_helper'
+require './modules/decision_reviews/spec/dr_spec_helper'
+require './modules/decision_reviews/spec/support/vcr_helper'
RSpec.describe 'DecisionReviews::V1::HigherLevelReviews::ContestableIssues', type: :request do
let(:user) { build(:user, :loa3) }
diff --git a/modules/decision_reviews/spec/requests/v1/higher_level_reviews_spec.rb b/modules/decision_reviews/spec/requests/v1/higher_level_reviews_spec.rb
index 5dac7bb11ae..bed7f2f2c6d 100644
--- a/modules/decision_reviews/spec/requests/v1/higher_level_reviews_spec.rb
+++ b/modules/decision_reviews/spec/requests/v1/higher_level_reviews_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require 'rails_helper'
-require 'support/controller_spec_helper'
+require './modules/decision_reviews/spec/dr_spec_helper'
+require './modules/decision_reviews/spec/support/vcr_helper'
RSpec.describe 'DecisonReviews::V1::HigherLevelReviews', type: :request do
let(:user) { build(:user, :loa3) }
diff --git a/modules/decision_reviews/spec/requests/v1/notice_of_disagreements/contestable_issues_spec.rb b/modules/decision_reviews/spec/requests/v1/notice_of_disagreements/contestable_issues_spec.rb
index 8b1ab534841..06638c9229a 100644
--- a/modules/decision_reviews/spec/requests/v1/notice_of_disagreements/contestable_issues_spec.rb
+++ b/modules/decision_reviews/spec/requests/v1/notice_of_disagreements/contestable_issues_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require 'rails_helper'
-require 'support/controller_spec_helper'
+require './modules/decision_reviews/spec/dr_spec_helper'
+require './modules/decision_reviews/spec/support/vcr_helper'
RSpec.describe 'DecisionReviews::V1::NoticeOfDisagreements::ContestableIssues', type: :request do
let(:user) { build(:user, :loa3) }
diff --git a/modules/decision_reviews/spec/requests/v1/notice_of_disagreements_spec.rb b/modules/decision_reviews/spec/requests/v1/notice_of_disagreements_spec.rb
index 5ec32afec67..1a7b1d539c2 100644
--- a/modules/decision_reviews/spec/requests/v1/notice_of_disagreements_spec.rb
+++ b/modules/decision_reviews/spec/requests/v1/notice_of_disagreements_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require 'rails_helper'
-require 'support/controller_spec_helper'
+require './modules/decision_reviews/spec/dr_spec_helper'
+require './modules/decision_reviews/spec/support/vcr_helper'
RSpec.describe 'DecisionReviews::V1::NoticeOfDisagreements', type: :request do
let(:user) do
diff --git a/modules/decision_reviews/spec/requests/v1/supplemental_claims/contestable_issues_spec.rb b/modules/decision_reviews/spec/requests/v1/supplemental_claims/contestable_issues_spec.rb
index b8459297f96..2b62181d771 100644
--- a/modules/decision_reviews/spec/requests/v1/supplemental_claims/contestable_issues_spec.rb
+++ b/modules/decision_reviews/spec/requests/v1/supplemental_claims/contestable_issues_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require 'rails_helper'
-require 'support/controller_spec_helper'
+require './modules/decision_reviews/spec/dr_spec_helper'
+require './modules/decision_reviews/spec/support/vcr_helper'
RSpec.describe 'DecisionReviews::V1::SupplementalClaims::ContestableIssues', type: :request do
let(:user) { build(:user, :loa3) }
diff --git a/modules/decision_reviews/spec/requests/v1/supplemental_claims_spec.rb b/modules/decision_reviews/spec/requests/v1/supplemental_claims_spec.rb
index 750ffc3a44e..a5a7860283c 100644
--- a/modules/decision_reviews/spec/requests/v1/supplemental_claims_spec.rb
+++ b/modules/decision_reviews/spec/requests/v1/supplemental_claims_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require 'rails_helper'
-require 'support/controller_spec_helper'
+require './modules/decision_reviews/spec/dr_spec_helper'
+require './modules/decision_reviews/spec/support/vcr_helper'
RSpec.describe 'DecisionReviews::V1::SupplementalClaims', type: :request do
let(:user) { build(:user, :loa3) }
diff --git a/modules/decision_reviews/spec/requests/v2/higher_level_reviews_spec.rb b/modules/decision_reviews/spec/requests/v2/higher_level_reviews_spec.rb
index 81bcd4f5711..0016edebd70 100644
--- a/modules/decision_reviews/spec/requests/v2/higher_level_reviews_spec.rb
+++ b/modules/decision_reviews/spec/requests/v2/higher_level_reviews_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require 'rails_helper'
-require 'support/controller_spec_helper'
+require './modules/decision_reviews/spec/dr_spec_helper'
+require './modules/decision_reviews/spec/support/vcr_helper'
RSpec.describe 'DecisionReviews::V2::HigherLevelReviews', type: :request do
let(:user) { build(:user, :loa3) }
diff --git a/modules/decision_reviews/spec/sidekiq/form4142_submit_spec.rb b/modules/decision_reviews/spec/sidekiq/form4142_submit_spec.rb
index 763cb002a60..45b242cd6fb 100644
--- a/modules/decision_reviews/spec/sidekiq/form4142_submit_spec.rb
+++ b/modules/decision_reviews/spec/sidekiq/form4142_submit_spec.rb
@@ -107,8 +107,6 @@
it 'increments statsd correctly when email is sent' do
expect { described_class.new.sidekiq_retries_exhausted_block.call(msg) }
.to trigger_statsd_increment('worker.decision_review.form4142_submit.permanent_error')
- .and trigger_statsd_increment('silent_failure', tags:)
- .and trigger_statsd_increment('silent_failure_avoided_no_confirmation', tags:)
.and trigger_statsd_increment('worker.decision_review.form4142_submit.retries_exhausted.email_queued')
end
diff --git a/modules/decision_reviews/spec/sidekiq/submit_upload_spec.rb b/modules/decision_reviews/spec/sidekiq/submit_upload_spec.rb
index d8a0c4abbd1..bb3e1712ab6 100644
--- a/modules/decision_reviews/spec/sidekiq/submit_upload_spec.rb
+++ b/modules/decision_reviews/spec/sidekiq/submit_upload_spec.rb
@@ -298,8 +298,6 @@
it 'increments statsd correctly when email is sent' do
expect { described_class.new.sidekiq_retries_exhausted_block.call(msg) }
.to trigger_statsd_increment('worker.decision_review.submit_upload.permanent_error')
- .and trigger_statsd_increment('silent_failure', tags:)
- .and trigger_statsd_increment('silent_failure_avoided_no_confirmation', tags:)
.and trigger_statsd_increment('worker.decision_review.submit_upload.retries_exhausted.email_queued')
end
diff --git a/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb b/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb
index 25eb41aca09..a3dceae648c 100644
--- a/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb
+++ b/modules/ivc_champva/app/controllers/ivc_champva/v1/uploads_controller.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'ddtrace'
+require 'datadog'
module IvcChampva
module V1
@@ -82,6 +82,7 @@ def submit_supporting_documents
private
if Flipper.enabled?(:champva_multiple_stamp_retry, @current_user)
+
def handle_file_uploads(form_id, parsed_form_data)
attempt = 0
max_attempts = 1
@@ -95,13 +96,13 @@ def handle_file_uploads(form_id, parsed_form_data)
error_message_downcase = e.message.downcase
Rails.logger.error "Error handling file uploads (attempt #{attempt}): #{e.message}"
- if error_message_downcase.include?('failed to generate stamped file') ||
- (error_message_downcase.include?('unable to find file') && attempt <= max_attempts)
+ if should_retry?(error_message_downcase, attempt, max_attempts)
Rails.logger.error 'Retrying in 1 seconds...'
sleep 1
retry
else
- return [[], 'Error handling file uploads']
+ statuses = []
+ error_message = 'retried once'
end
end
@@ -125,6 +126,17 @@ def handle_file_uploads(form_id, parsed_form_data)
end
end
+ def should_retry?(error_message_downcase, attempt, max_attempts)
+ error_conditions = [
+ 'failed to generate',
+ 'no such file',
+ 'an error occurred while verifying stamp:',
+ 'unable to find file'
+ ]
+
+ error_conditions.any? { |condition| error_message_downcase.include?(condition) } && attempt <= max_attempts
+ end
+
def get_attachment_ids_and_form(parsed_form_data)
form_id = get_form_id
form_class = "IvcChampva::#{form_id.titleize.gsub(' ', '')}".constantize
@@ -171,6 +183,7 @@ def supporting_document_ids(parsed_form_data)
def get_file_paths_and_metadata(parsed_form_data)
attachment_ids, form = get_attachment_ids_and_form(parsed_form_data)
+
filler = IvcChampva::PdfFiller.new(form_number: form.form_id, form:, uuid: form.uuid)
file_path = if @current_user
filler.generate(@current_user.loa[:current])
diff --git a/modules/ivc_champva/lib/pega_api/client.rb b/modules/ivc_champva/lib/pega_api/client.rb
new file mode 100644
index 00000000000..192cce473ba
--- /dev/null
+++ b/modules/ivc_champva/lib/pega_api/client.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'common/client/base'
+require_relative 'configuration'
+
+module IvcChampva
+ module PegaApi
+ class PegaApiError < StandardError; end
+
+ class Client < Common::Client::Base
+ configuration IvcChampva::PegaApi::Configuration
+
+ ##
+ # HTTP POST call to the Pega API to retrieve a report
+ #
+ # @param date_start [Date, nil] the start date of the report
+ # @param date_end [Date, nil] the end date of the report
+ # @return [Array] the report rows
+ def get_report(date_start, date_end)
+ resp = connection.post(config.base_path) do |req|
+ req.headers = headers(date_start, date_end)
+ end
+
+ raise "response code: #{resp.status}, response body: #{resp.body}" unless resp.status == 200
+
+ # We also need to check the StatusCode in the response body.
+ # It seems that when this API errors out, it will return responses with HTTP 200 statuses, but
+ # the StatusCode in the response body will be something other than 200.
+ response = JSON.parse(resp.body, symbolize_names: false)
+ unless response['statusCode'] == 200
+ raise "alternate response code: #{response['statusCode']}, response body: #{response['body']}"
+ end
+
+ # With both status codes checked and passing, we should now have a body that is more JSON embedded in a string.
+ # This is our report, let's decode it.
+ JSON.parse(response['body'])
+ rescue => e
+ raise PegaApiError, e.message.to_s
+ end
+
+ ##
+ # Assembles headers for the Pega API request
+ #
+ # @param date_start [Date, nil] the start date of the report
+ # @param date_end [Date, nil] the end date of the report
+ # @return [Hash] the headers
+ def headers(date_start, date_end)
+ {
+ :content_type => 'application/json',
+ 'x-api-key' => Settings.ivc_champva.pega_api.api_key.to_s,
+ 'date_start' => date_start.to_s,
+ 'date_end' => date_end.to_s,
+ 'case_id' => '' # case_id seems to have no effect, but it is required by the API
+ }
+ end
+ end
+ end
+end
diff --git a/modules/ivc_champva/lib/pega_api/configuration.rb b/modules/ivc_champva/lib/pega_api/configuration.rb
new file mode 100644
index 00000000000..dd13d06d2da
--- /dev/null
+++ b/modules/ivc_champva/lib/pega_api/configuration.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'common/client/configuration/rest'
+require 'common/client/middleware/response/raise_custom_error'
+
+module IvcChampva
+ module PegaApi
+ class Configuration < Common::Client::Configuration::REST
+ def base_path
+ 'https://bt41mfpkj5.execute-api.us-gov-west-1.amazonaws.com/prod/'
+ end
+
+ def connection
+ Faraday.new(base_path, headers: base_request_headers, request: request_options) do |conn|
+ conn.use :breakers
+ # conn.use :instrumentation, name: 'dhp.fitbit.request.faraday'
+
+ # Uncomment this if you want curlggg command equivalent or response output to log
+ # conn.request(:curl, ::Logger.new(STDOUT), :warn) unless Rails.env.production?
+ # conn.response(:logger, ::Logger.new(STDOUT), bodies: true) unless Rails.env.production?
+
+ # conn.response :raise_custom_error, error_prefix: service_name
+
+ conn.adapter Faraday.default_adapter
+ end
+ end
+ end
+ end
+end
diff --git a/modules/ivc_champva/spec/fixtures/pega_api_json/report_response_200_200.json b/modules/ivc_champva/spec/fixtures/pega_api_json/report_response_200_200.json
new file mode 100644
index 00000000000..5845b5ab8ef
--- /dev/null
+++ b/modules/ivc_champva/spec/fixtures/pega_api_json/report_response_200_200.json
@@ -0,0 +1,4 @@
+{
+ "statusCode": 200,
+ "body": "[{\"Creation Date\": \"2024-11-27T08:42:11.372000\", \"PEGA Case ID\": \"D-55824\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-27T08:42:13.737000\", \"PEGA Case ID\": \"D-55825\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-27T08:42:15.941000\", \"PEGA Case ID\": \"D-55826\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:04:20.156000\", \"PEGA Case ID\": \"D-56133\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-27T08:42:17.918000\", \"PEGA Case ID\": \"D-55827\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:28:39.290000\", \"PEGA Case ID\": \"D-56146\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:04:18.025000\", \"PEGA Case ID\": \"D-56132\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:09:25.571000\", \"PEGA Case ID\": \"D-56140\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:04:22.210000\", \"PEGA Case ID\": \"D-56134\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:04:24.295000\", \"PEGA Case ID\": \"D-56135\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:04:26.420000\", \"PEGA Case ID\": \"D-56136\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:09:19.407000\", \"PEGA Case ID\": \"D-56137\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:09:21.432000\", \"PEGA Case ID\": \"D-56138\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:09:23.509000\", \"PEGA Case ID\": \"D-56139\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:09:27.575000\", \"PEGA Case ID\": \"D-56141\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:09:29.621000\", \"PEGA Case ID\": \"D-56142\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:09:31.668000\", \"PEGA Case ID\": \"D-56143\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:28:41.317000\", \"PEGA Case ID\": \"D-56147\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:09:33.857000\", \"PEGA Case ID\": \"D-56144\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:09:35.920000\", \"PEGA Case ID\": \"D-56145\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:28:43.262000\", \"PEGA Case ID\": \"D-56148\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:28:45.264000\", \"PEGA Case ID\": \"D-56149\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-13T13:39:45.349000\", \"PEGA Case ID\": \"D-57726\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-11-27T09:20:07.057000\", \"PEGA Case ID\": \"D-55838\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:28:47.274000\", \"PEGA Case ID\": \"D-56150\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:28:49.242000\", \"PEGA Case ID\": \"D-56151\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-27T12:43:15.204000\", \"PEGA Case ID\": \"D-55840\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T07:28:51.242000\", \"PEGA Case ID\": \"D-56152\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:18:11.247000\", \"PEGA Case ID\": \"D-56153\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:18:13.310000\", \"PEGA Case ID\": \"D-56154\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-27T12:43:17.299000\", \"PEGA Case ID\": \"D-55841\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:18:15.360000\", \"PEGA Case ID\": \"D-56155\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-27T12:43:19.380000\", \"PEGA Case ID\": \"D-55842\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:18:17.428000\", \"PEGA Case ID\": \"D-56156\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:18:19.540000\", \"PEGA Case ID\": \"D-56157\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:47:56.198000\", \"PEGA Case ID\": \"D-56158\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:47:58.301000\", \"PEGA Case ID\": \"D-56159\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:48:00.330000\", \"PEGA Case ID\": \"D-56160\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-13T13:39:47.702000\", \"PEGA Case ID\": \"D-57727\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-03T09:48:02.389000\", \"PEGA Case ID\": \"D-56161\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:48:04.448000\", \"PEGA Case ID\": \"D-56162\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:48:06.449000\", \"PEGA Case ID\": \"D-56163\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T09:48:08.543000\", \"PEGA Case ID\": \"D-56164\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-13T14:57:19.504000\", \"PEGA Case ID\": \"D-57728\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T10:26:12.362000\", \"PEGA Case ID\": \"D-56177\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-13T15:06:34.224000\", \"PEGA Case ID\": \"D-57729\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-13T15:06:36.359000\", \"PEGA Case ID\": \"D-57730\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-13T15:10:54.677000\", \"PEGA Case ID\": \"D-57731\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-13T15:10:56.781000\", \"PEGA Case ID\": \"D-57732\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-13T15:10:58.919000\", \"PEGA Case ID\": \"D-57733\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-13T15:40:51.094000\", \"PEGA Case ID\": \"D-57734\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T10:53:19.196000\", \"PEGA Case ID\": \"D-56178\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T10:53:23.446000\", \"PEGA Case ID\": \"D-56180\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T10:53:21.400000\", \"PEGA Case ID\": \"D-56179\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-14T11:37:50.922000\", \"PEGA Case ID\": \"D-57735\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T10:53:25.449000\", \"PEGA Case ID\": \"D-56181\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T10:53:27.505000\", \"PEGA Case ID\": \"D-56182\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T10:53:29.551000\", \"PEGA Case ID\": \"D-56183\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T10:53:31.568000\", \"PEGA Case ID\": \"D-56184\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T10:53:33.540000\", \"PEGA Case ID\": \"D-56185\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T11:03:56.885000\", \"PEGA Case ID\": \"D-56186\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-14T11:37:53.166000\", \"PEGA Case ID\": \"D-57736\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T11:03:59.007000\", \"PEGA Case ID\": \"D-56187\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-14T11:37:55.296000\", \"PEGA Case ID\": \"D-57737\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T11:04:01.203000\", \"PEGA Case ID\": \"D-56188\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-14T19:51:31.931000\", \"PEGA Case ID\": \"D-57738\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T11:08:38.753000\", \"PEGA Case ID\": \"D-56189\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-16T08:17:02.411000\", \"PEGA Case ID\": \"D-57739\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-03T11:08:40.912000\", \"PEGA Case ID\": \"D-56190\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T11:08:42.948000\", \"PEGA Case ID\": \"D-56191\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T11:11:12.726000\", \"PEGA Case ID\": \"D-56192\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-16T08:17:04.664000\", \"PEGA Case ID\": \"D-57740\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-03T11:11:14.767000\", \"PEGA Case ID\": \"D-56193\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T11:11:16.747000\", \"PEGA Case ID\": \"D-56194\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T11:11:18.730000\", \"PEGA Case ID\": \"D-56195\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T11:11:20.704000\", \"PEGA Case ID\": \"D-56196\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T14:25:48.306000\", \"PEGA Case ID\": \"D-56239\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-27T12:43:13.042000\", \"PEGA Case ID\": \"D-55839\", \"Status\": \"Resolved-Duplicate\"}, {\"Creation Date\": \"2024-11-26T14:55:31.156000\", \"PEGA Case ID\": \"D-55820\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-26T10:29:59.762000\", \"PEGA Case ID\": \"D-55787\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-26T10:30:02.277000\", \"PEGA Case ID\": \"D-55788\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-26T10:30:04.353000\", \"PEGA Case ID\": \"D-55789\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-26T10:30:06.451000\", \"PEGA Case ID\": \"D-55790\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:04.078000\", \"PEGA Case ID\": \"D-56037\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:10.372000\", \"PEGA Case ID\": \"D-56041\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T13:58:58.940000\", \"PEGA Case ID\": \"D-56237\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T14:11:04.263000\", \"PEGA Case ID\": \"D-56238\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:06.192000\", \"PEGA Case ID\": \"D-56039\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T14:27:42.899000\", \"PEGA Case ID\": \"D-56240\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:08.277000\", \"PEGA Case ID\": \"D-56040\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:18.796000\", \"PEGA Case ID\": \"D-56045\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:20.831000\", \"PEGA Case ID\": \"D-56046\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:22.960000\", \"PEGA Case ID\": \"D-56047\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:25.007000\", \"PEGA Case ID\": \"D-56048\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-26T14:55:33.215000\", \"PEGA Case ID\": \"D-55821\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-26T14:55:35.210000\", \"PEGA Case ID\": \"D-55822\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-11-26T15:22:22.066000\", \"PEGA Case ID\": \"D-55823\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:12.641000\", \"PEGA Case ID\": \"D-56042\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:14.692000\", \"PEGA Case ID\": \"D-56043\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T14:29:20.248000\", \"PEGA Case ID\": \"D-56241\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:16.818000\", \"PEGA Case ID\": \"D-56044\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-03T14:30:46.388000\", \"PEGA Case ID\": \"D-56242\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:27.196000\", \"PEGA Case ID\": \"D-56050\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:29.274000\", \"PEGA Case ID\": \"D-56051\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:30:31.478000\", \"PEGA Case ID\": \"D-56052\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:53:22.373000\", \"PEGA Case ID\": \"D-56083\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:53:24.470000\", \"PEGA Case ID\": \"D-56084\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:53:18.184000\", \"PEGA Case ID\": \"D-56081\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:53:20.242000\", \"PEGA Case ID\": \"D-56082\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:53:26.567000\", \"PEGA Case ID\": \"D-56085\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:53:28.604000\", \"PEGA Case ID\": \"D-56086\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:53:30.671000\", \"PEGA Case ID\": \"D-56094\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T14:53:32.706000\", \"PEGA Case ID\": \"D-56095\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T16:08:39.674000\", \"PEGA Case ID\": \"D-56117\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T16:08:37.746000\", \"PEGA Case ID\": \"D-56116\", \"Status\": \"Pending BCPU Review\"}, {\"Creation Date\": \"2024-12-10T10:40:20.822000\", \"PEGA Case ID\": \"D-57641\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T16:04:22.745000\", \"PEGA Case ID\": \"D-56115\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-02T16:13:49.932000\", \"PEGA Case ID\": \"D-56118\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-10T10:47:54.825000\", \"PEGA Case ID\": \"D-57643\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-10T10:43:29.787000\", \"PEGA Case ID\": \"D-57642\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-11T14:26:14.179000\", \"PEGA Case ID\": \"D-57667\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-11T14:26:16.309000\", \"PEGA Case ID\": \"D-57668\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-04T07:51:51.955000\", \"PEGA Case ID\": \"D-56521\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-12T09:19:57.663000\", \"PEGA Case ID\": \"D-57669\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-12T13:53:11.055000\", \"PEGA Case ID\": \"D-57679\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T12:22:06.874000\", \"PEGA Case ID\": \"D-57020\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-04T13:32:34.586000\", \"PEGA Case ID\": \"D-57026\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:32:36.600000\", \"PEGA Case ID\": \"D-57027\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:32:38.600000\", \"PEGA Case ID\": \"D-57028\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-12T14:00:57.962000\", \"PEGA Case ID\": \"D-57682\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-13T06:44:07.190000\", \"PEGA Case ID\": \"D-57686\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-12T09:20:00.073000\", \"PEGA Case ID\": \"D-57670\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-04T11:27:26.523000\", \"PEGA Case ID\": \"D-57012\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:13.674000\", \"PEGA Case ID\": \"D-57054\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:35:59.827000\", \"PEGA Case ID\": \"D-57033\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:36:01.881000\", \"PEGA Case ID\": \"D-57034\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:25:50.351000\", \"PEGA Case ID\": \"D-57043\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:25:52.405000\", \"PEGA Case ID\": \"D-57044\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:24:57.183000\", \"PEGA Case ID\": \"D-57002\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:24:59.223000\", \"PEGA Case ID\": \"D-57003\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:25:01.304000\", \"PEGA Case ID\": \"D-57004\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T15:37:45.377000\", \"PEGA Case ID\": \"D-57083\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:35:53.683000\", \"PEGA Case ID\": \"D-57030\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:35:55.765000\", \"PEGA Case ID\": \"D-57031\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:35:57.777000\", \"PEGA Case ID\": \"D-57032\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:36:03.884000\", \"PEGA Case ID\": \"D-57035\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:39:45.202000\", \"PEGA Case ID\": \"D-57039\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:39:49.236000\", \"PEGA Case ID\": \"D-57041\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T15:37:43.379000\", \"PEGA Case ID\": \"D-57082\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-12T13:10:16.062000\", \"PEGA Case ID\": \"D-57674\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:24:50.969000\", \"PEGA Case ID\": \"D-57001\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:25:03.425000\", \"PEGA Case ID\": \"D-57005\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:25:05.549000\", \"PEGA Case ID\": \"D-57006\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:27:22.468000\", \"PEGA Case ID\": \"D-57010\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-13T06:45:46.022000\", \"PEGA Case ID\": \"D-57688\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-12T13:17:42.594000\", \"PEGA Case ID\": \"D-57675\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-12T13:17:44.769000\", \"PEGA Case ID\": \"D-57676\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-04T13:32:40.640000\", \"PEGA Case ID\": \"D-57029\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:07.316000\", \"PEGA Case ID\": \"D-57051\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:11.495000\", \"PEGA Case ID\": \"D-57053\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:17.830000\", \"PEGA Case ID\": \"D-57056\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:19.795000\", \"PEGA Case ID\": \"D-57057\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:21.933000\", \"PEGA Case ID\": \"D-57058\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:27:20.395000\", \"PEGA Case ID\": \"D-57009\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:27:24.523000\", \"PEGA Case ID\": \"D-57011\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T11:27:46.112000\", \"PEGA Case ID\": \"D-57013\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-12T13:19:22.890000\", \"PEGA Case ID\": \"D-57677\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:31:19.214000\", \"PEGA Case ID\": \"D-57022\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:39:39.222000\", \"PEGA Case ID\": \"D-57036\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:39:41.208000\", \"PEGA Case ID\": \"D-57037\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:39:47.258000\", \"PEGA Case ID\": \"D-57040\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-12T13:53:13.206000\", \"PEGA Case ID\": \"D-57680\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-13T06:44:05.048000\", \"PEGA Case ID\": \"D-57685\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-12T13:23:56.845000\", \"PEGA Case ID\": \"D-57678\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-12T13:53:15.200000\", \"PEGA Case ID\": \"D-57681\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:39:43.219000\", \"PEGA Case ID\": \"D-57038\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T15:37:41.276000\", \"PEGA Case ID\": \"D-57081\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-13T06:45:44.079000\", \"PEGA Case ID\": \"D-57687\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-04T13:31:21.305000\", \"PEGA Case ID\": \"D-57023\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:31:23.338000\", \"PEGA Case ID\": \"D-57024\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T13:31:25.447000\", \"PEGA Case ID\": \"D-57025\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:05.252000\", \"PEGA Case ID\": \"D-57050\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:09.412000\", \"PEGA Case ID\": \"D-57052\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:15.702000\", \"PEGA Case ID\": \"D-57055\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T15:37:37.119000\", \"PEGA Case ID\": \"D-57079\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T12:22:08.949000\", \"PEGA Case ID\": \"D-57021\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-04T14:25:48.089000\", \"PEGA Case ID\": \"D-57042\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:25:54.502000\", \"PEGA Case ID\": \"D-57045\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:25:56.718000\", \"PEGA Case ID\": \"D-57046\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:25:58.760000\", \"PEGA Case ID\": \"D-57047\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:00.894000\", \"PEGA Case ID\": \"D-57048\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T14:26:02.932000\", \"PEGA Case ID\": \"D-57049\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T15:37:35.025000\", \"PEGA Case ID\": \"D-57078\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-04T15:37:39.157000\", \"PEGA Case ID\": \"D-57080\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-05T09:00:07.337000\", \"PEGA Case ID\": \"D-57084\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-06T09:18:43.532000\", \"PEGA Case ID\": \"D-57104\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-05T09:48:38.786000\", \"PEGA Case ID\": \"D-57087\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-05T10:48:52.033000\", \"PEGA Case ID\": \"D-57089\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-05T10:48:54.161000\", \"PEGA Case ID\": \"D-57090\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-05T09:50:04.360000\", \"PEGA Case ID\": \"D-57088\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-06T12:01:08.196000\", \"PEGA Case ID\": \"D-57108\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-06T12:01:10.294000\", \"PEGA Case ID\": \"D-57109\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-05T10:52:08.728000\", \"PEGA Case ID\": \"D-57091\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-05T10:52:10.797000\", \"PEGA Case ID\": \"D-57092\", \"Status\": \"Open\"}, {\"Creation Date\": \"2024-12-05T11:49:38.419000\", \"PEGA Case ID\": \"D-57093\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-06T07:32:00.763000\", \"PEGA Case ID\": \"D-57094\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-06T09:18:45.619000\", \"PEGA Case ID\": \"D-57105\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-06T07:32:03.105000\", \"PEGA Case ID\": \"D-57095\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-06T07:32:05.258000\", \"PEGA Case ID\": \"D-57096\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-06T07:36:13.052000\", \"PEGA Case ID\": \"D-57097\", \"Status\": \"FMP Incoming\"}, {\"Creation Date\": \"2024-12-06T07:36:15.274000\", \"PEGA Case ID\": \"D-57098\", \"Status\": \"FMP Incoming\"}]"
+}
\ No newline at end of file
diff --git a/modules/ivc_champva/spec/fixtures/pega_api_json/report_response_200_500.json b/modules/ivc_champva/spec/fixtures/pega_api_json/report_response_200_500.json
new file mode 100644
index 00000000000..9d7fa0b20f7
--- /dev/null
+++ b/modules/ivc_champva/spec/fixtures/pega_api_json/report_response_200_500.json
@@ -0,0 +1,4 @@
+{
+ "statusCode": 500,
+ "body": "'case_id'"
+}
\ No newline at end of file
diff --git a/modules/ivc_champva/spec/fixtures/pega_api_json/report_response_403.json b/modules/ivc_champva/spec/fixtures/pega_api_json/report_response_403.json
new file mode 100644
index 00000000000..7643b20f2e5
--- /dev/null
+++ b/modules/ivc_champva/spec/fixtures/pega_api_json/report_response_403.json
@@ -0,0 +1,3 @@
+{
+ "message": "Forbidden"
+}
\ No newline at end of file
diff --git a/modules/ivc_champva/spec/lib/pega_api_client_spec.rb b/modules/ivc_champva/spec/lib/pega_api_client_spec.rb
new file mode 100644
index 00000000000..caaf54fe5f0
--- /dev/null
+++ b/modules/ivc_champva/spec/lib/pega_api_client_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'pega_api/client'
+
+RSpec.describe IvcChampva::PegaApi::Client do
+ subject { described_class.new }
+
+ describe 'get_report' do
+ let(:body200and200) do # pega api response with HTTP status 200 and alternate status 200
+ fixture_path = Rails.root.join('modules', 'ivc_champva', 'spec', 'fixtures', 'pega_api_json',
+ 'report_response_200_200.json')
+ fixture_path.read
+ end
+
+ let(:body200and500) do # pega api response with HTTP status 200 and alternate status 500
+ fixture_path = Rails.root.join('modules', 'ivc_champva', 'spec', 'fixtures', 'pega_api_json',
+ 'report_response_200_500.json')
+ fixture_path.read
+ end
+
+ let(:body403) do # pega api response with HTTP status 403 forbidden
+ fixture_path = Rails.root.join('modules', 'ivc_champva', 'spec', 'fixtures', 'pega_api_json',
+ 'report_response_403.json')
+ fixture_path.read
+ end
+
+ context 'successful response from pega' do
+ let(:faraday_response) { double('Faraday::Response', status: 200, body: body200and200) }
+
+ before do
+ allow_any_instance_of(Faraday::Connection).to receive(:post).with(anything).and_return(faraday_response)
+ end
+
+ it 'returns the body as an array of hashes' do
+ result = subject.get_report(Date.new(2024, 11, 1), Date.new(2024, 12, 31))
+
+ expect(result[0]['Creation Date']).to eq('2024-11-27T08:42:11.372000')
+ expect(result[0]['PEGA Case ID']).to eq('D-55824')
+ expect(result[0]['Status']).to eq('Open')
+ end
+ end
+
+ context 'unsuccessful pega response with bad HTTP status' do
+ let(:faraday_response) { double('Faraday::Response', status: 403, body: body403) }
+
+ before do
+ allow_any_instance_of(Faraday::Connection).to receive(:post).with(anything).and_return(faraday_response)
+ end
+
+ it 'raises error when response is 404' do
+ expect { subject.get_report(nil, nil) }.to raise_error(IvcChampva::PegaApi::PegaApiError)
+ end
+ end
+
+ context 'unsuccessful pega response with bad alternate status' do
+ let(:faraday_response) { double('Faraday::Response', status: 200, body: body200and500) }
+
+ before do
+ allow_any_instance_of(Faraday::Connection).to receive(:post).with(anything).and_return(faraday_response)
+ end
+
+ it 'raises error when alternate status is 500' do
+ expect { subject.get_report(nil, nil) }.to raise_error(IvcChampva::PegaApi::PegaApiError)
+ end
+ end
+ end
+
+ describe 'headers' do
+ it 'returns the right headers' do
+ result = subject.headers(Date.new(2024, 11, 1), Date.new(2024, 12, 31))
+
+ expect(result[:content_type]).to eq('application/json')
+ expect(result['x-api-key']).to eq('fake_api_key')
+ expect(result['date_start']).to eq('2024-11-01')
+ expect(result['date_end']).to eq('2024-12-31')
+ expect(result['case_id']).to eq('')
+ end
+
+ it 'returns the right headers with nil dates' do
+ result = subject.headers(nil, nil)
+
+ expect(result[:content_type]).to eq('application/json')
+ expect(result['x-api-key']).to eq('fake_api_key')
+ expect(result['date_start']).to eq('')
+ expect(result['date_end']).to eq('')
+ expect(result['case_id']).to eq('')
+ end
+ end
+
+ # Temporary, delete me
+ # This test is used to hit the production endpoint when running locally.
+ # It can be removed once we have some real code that uses the Pega API client.
+ describe 'hit the production endpoint', skip: 'this is useful as a way to hit the API during local development' do
+ let(:forced_headers) do
+ {
+ :content_type => 'application/json',
+ # use the following line when running locally tp pull the key from an environment variable
+ 'x-api-key' => ENV.fetch('PEGA_API_KEY'), # to set: export PEGA_API_KEY=insert1the2api3key4here
+ 'date_start' => '', # '2024-11-01', # '11/01/2024',
+ 'date_end' => '', # '2024-12-31', # '12/07/2024',
+ 'case_id' => ''
+ }
+ end
+
+ before do
+ allow_any_instance_of(IvcChampva::PegaApi::Client).to receive(:headers).with(anything, anything)
+ .and_return(forced_headers)
+ end
+
+ it 'returns report data' do
+ VCR.configure do |c|
+ c.allow_http_connections_when_no_cassette = true
+ end
+
+ result = subject.get_report(Date.new(2024, 11, 1), Date.new(2024, 12, 31))
+ expect(result.count).to be_positive
+
+ # byebug # in byebug, type 'p result' to view the response
+ end
+ end
+end
diff --git a/modules/ivc_champva/spec/requests/ivc_champva/v1/forms/uploads_spec.rb b/modules/ivc_champva/spec/requests/ivc_champva/v1/forms/uploads_spec.rb
index b603e2d2537..8b4054e32fd 100644
--- a/modules/ivc_champva/spec/requests/ivc_champva/v1/forms/uploads_spec.rb
+++ b/modules/ivc_champva/spec/requests/ivc_champva/v1/forms/uploads_spec.rb
@@ -372,4 +372,37 @@
end
end
end
+
+ describe '#should_retry?' do
+ let(:controller) { IvcChampva::V1::UploadsController.new }
+
+ it 'returns true for retryable errors within max attempts' do
+ retryable_errors = [
+ 'failed to generate file',
+ 'no such file or directory',
+ 'an error occurred while verifying stamp: some error',
+ 'unable to find file'
+ ]
+
+ retryable_errors.each do |error_message|
+ expect(controller.send(:should_retry?, error_message.downcase, 1, 3)).to be true
+ end
+ end
+
+ it 'returns false for non-retryable errors' do
+ non_retryable_errors = [
+ 'some other error',
+ 'random error message'
+ ]
+
+ non_retryable_errors.each do |error_message|
+ expect(controller.send(:should_retry?, error_message.downcase, 1, 3)).to be false
+ end
+ end
+
+ it 'returns false when max attempts exceeded' do
+ error_message = 'failed to generate file'
+ expect(controller.send(:should_retry?, error_message.downcase, 4, 3)).to be false
+ end
+ end
end
diff --git a/modules/mobile/app/services/mobile/v0/profile/sync_update_service.rb b/modules/mobile/app/services/mobile/v0/profile/sync_update_service.rb
index 3491ec15528..e677f57729a 100644
--- a/modules/mobile/app/services/mobile/v0/profile/sync_update_service.rb
+++ b/modules/mobile/app/services/mobile/v0/profile/sync_update_service.rb
@@ -58,7 +58,7 @@ def save!(http_method, resource_type, params)
end
def build_record(type, params)
- if type == :address && Flipper.enabled?(:va_v3_contact_information_service, @user)
+ if type == :address && Flipper.enabled?(:mobile_v2_contact_info, @user)
'VAProfile::Models::V3::Address'
.constantize
.new(params)
diff --git a/modules/mobile/spec/controllers/application_controller_spec.rb b/modules/mobile/spec/controllers/application_controller_spec.rb
index eff70329997..95e28cf8fe3 100644
--- a/modules/mobile/spec/controllers/application_controller_spec.rb
+++ b/modules/mobile/spec/controllers/application_controller_spec.rb
@@ -169,10 +169,14 @@ def append_info_to_payload(payload)
let(:access_token) { create(:access_token, audience: ['vamobile']) }
let(:bearer_token) { SignIn::AccessTokenJwtEncoder.new(access_token:).perform }
let!(:user) { create(:user, :loa3, uuid: access_token.user_uuid) }
+ let(:deceased_date) { nil }
+ let(:id_theft_flag) { false }
+ let(:mpi_profile) { build(:mpi_profile, deceased_date:, id_theft_flag:) }
before do
request.headers['Authorization'] = "Bearer #{bearer_token}"
request.headers['Authentication-Method'] = 'SIS'
+ allow_any_instance_of(MPIData).to receive(:profile).and_return(mpi_profile)
end
it 'uses SIS session authentication' do
@@ -194,6 +198,34 @@ def append_info_to_payload(payload)
expect(response).to have_http_status(:unauthorized)
end
end
+
+ context 'when validating the user\'s MPI profile' do
+ context 'and the MPI profile has a deceased date' do
+ let(:deceased_date) { '20020202' }
+ let(:expected_error) { 'Death Flag Detected' }
+
+ it 'raises an MPI locked account error' do
+ get :index
+
+ expect(response).to have_http_status(:internal_server_error)
+ error_body = JSON.parse(response.body)['errors'].first
+ expect(error_body['meta']['exception']).to eq(expected_error)
+ end
+ end
+
+ context 'and the MPI profile has an id theft flag' do
+ let(:id_theft_flag) { true }
+ let(:expected_error) { 'Theft Flag Detected' }
+
+ it 'raises an MPI locked account error' do
+ get :index
+
+ expect(response).to have_http_status(:internal_server_error)
+ error_body = JSON.parse(response.body)['errors'].first
+ expect(error_body['meta']['exception']).to eq(expected_error)
+ end
+ end
+ end
end
end
end
diff --git a/modules/mobile/spec/requests/mobile/v0/claims/pre_need_burial_spec.rb b/modules/mobile/spec/requests/mobile/v0/claims/pre_need_burial_spec.rb
index dc5f708299c..04ae9a767c5 100644
--- a/modules/mobile/spec/requests/mobile/v0/claims/pre_need_burial_spec.rb
+++ b/modules/mobile/spec/requests/mobile/v0/claims/pre_need_burial_spec.rb
@@ -8,7 +8,6 @@
include CommitteeHelper
describe 'POST /mobile/v0/claims/pre-need-burial' do
- Flipper.disable(:va_v3_contact_information_service)
let!(:user) { sis_user(icn: '1012846043V576341') }
let(:params) do
{ application: attributes_for(:burial_form) }
diff --git a/modules/mobile/spec/requests/mobile/v0/community_care_providers_spec.rb b/modules/mobile/spec/requests/mobile/v0/community_care_providers_spec.rb
index c76d8e30d0f..bccff0afdf6 100644
--- a/modules/mobile/spec/requests/mobile/v0/community_care_providers_spec.rb
+++ b/modules/mobile/spec/requests/mobile/v0/community_care_providers_spec.rb
@@ -10,6 +10,11 @@
let!(:user) { sis_user(icn: '9000682') }
let(:json_body_headers) { { 'Content-Type' => 'application/json', 'Accept' => 'application/json' } }
+ before do
+ allow(Flipper).to receive(:enabled?).with(:va_v3_contact_information_service, instance_of(User)).and_return(false)
+ allow(Flipper).to receive(:enabled?).with(:remove_pciu, instance_of(User)).and_return(false)
+ end
+
describe 'GET providers', :aggregate_failures do
it 'returns 200 with paginated results' do
VCR.use_cassette('mobile/facilities/ppms/community_clinics_near_user', match_requests_on: %i[method uri]) do
diff --git a/modules/mobile/spec/requests/mobile/v0/user/addresses_spec.rb b/modules/mobile/spec/requests/mobile/v0/user/addresses_spec.rb
index 248e6e1ff8c..8e13668b392 100644
--- a/modules/mobile/spec/requests/mobile/v0/user/addresses_spec.rb
+++ b/modules/mobile/spec/requests/mobile/v0/user/addresses_spec.rb
@@ -7,8 +7,13 @@
let!(:user) { sis_user }
- describe 'update endpoints' do
- Flipper.disable(:va_v3_contact_information_service)
+ before do
+ allow(Flipper).to receive(:enabled?).with(:mobile_v2_contact_info, instance_of(User)).and_return(false)
+ allow(Flipper).to receive(:enabled?).with(:va_v3_contact_information_service, instance_of(User)).and_return(false)
+ allow(Flipper).to receive(:enabled?).with(:remove_pciu, instance_of(User)).and_return(false)
+ end
+
+ describe 'update endpoints', :skip_va_profile_user do
let(:address) do
address = build(:va_profile_address, vet360_id: user.vet360_id)
# Some domestic addresses are coming in with province of string 'null'.
@@ -255,8 +260,7 @@
end
end
- describe 'POST /mobile/v0/user/addresses/validate' do
- Flipper.disable(:va_v3_contact_information_service)
+ describe 'POST /mobile/v0/user/addresses/validate', :skip_va_profile_user do
let(:address) do
address = build(:va_profile_address, vet360_id: user.vet360_id)
# Some domestic addresses are coming in with province of string 'null'.
diff --git a/modules/mobile/spec/requests/mobile/v0/user/contact_info_spec.rb b/modules/mobile/spec/requests/mobile/v0/user/contact_info_spec.rb
index f5a88e4e737..7dc6b810f82 100644
--- a/modules/mobile/spec/requests/mobile/v0/user/contact_info_spec.rb
+++ b/modules/mobile/spec/requests/mobile/v0/user/contact_info_spec.rb
@@ -2,7 +2,10 @@
require_relative '../../../../support/helpers/rails_helper'
RSpec.describe 'Mobile::V0::User::ContactInfo', type: :request do
- Flipper.disable(:va_v3_contact_information_service)
+ before do
+ allow(Flipper).to receive(:enabled?).with(:va_v3_contact_information_service, instance_of(User)).and_return(false)
+ allow(Flipper).to receive(:enabled?).with(:remove_pciu, instance_of(User)).and_return(false)
+ end
let!(:user) { sis_user }
let(:attributes) { response.parsed_body.dig('data', 'attributes') }
@@ -86,7 +89,7 @@
}
end
- describe 'GET /mobile/v0/user/contact_info with vet360 id' do
+ describe 'GET /mobile/v0/user/contact_info with vet360 id', :skip_va_profile_user do
context 'valid user' do
before do
get('/mobile/v0/user/contact-info', headers: sis_headers)
@@ -107,7 +110,7 @@
end
end
- describe 'GET /mobile/v0/user/contact_info without vet360 id' do
+ describe 'GET /mobile/v0/user/contact_info without vet360 id', :skip_va_profile_user do
let!(:user) { sis_user(vet360_id: nil) }
before do
diff --git a/modules/mobile/spec/requests/mobile/v0/user/phones_spec.rb b/modules/mobile/spec/requests/mobile/v0/user/phones_spec.rb
index c252b744113..f70219945be 100644
--- a/modules/mobile/spec/requests/mobile/v0/user/phones_spec.rb
+++ b/modules/mobile/spec/requests/mobile/v0/user/phones_spec.rb
@@ -12,8 +12,12 @@
end
let(:telephone) { build(:telephone, vet360_id: user.vet360_id) }
- Flipper.disable(:va_v3_contact_information_service)
- describe 'POST /mobile/v0/user/phones' do
+ before do
+ allow(Flipper).to receive(:enabled?).with(:va_v3_contact_information_service, instance_of(User)).and_return(false)
+ allow(Flipper).to receive(:enabled?).with(:remove_pciu, instance_of(User)).and_return(false)
+ end
+
+ describe 'POST /mobile/v0/user/phones', :skip_va_profile_user do
context 'with a valid phone number' do
before do
telephone.id = 42
diff --git a/modules/mobile/spec/requests/mobile/v0/user_spec.rb b/modules/mobile/spec/requests/mobile/v0/user_spec.rb
index a87ea1b1cba..3e9c62eea12 100644
--- a/modules/mobile/spec/requests/mobile/v0/user_spec.rb
+++ b/modules/mobile/spec/requests/mobile/v0/user_spec.rb
@@ -10,7 +10,12 @@
VAProfile::ContactInformation::Service
end
- describe 'GET /mobile/v0/user' do
+ before do
+ Flipper.disable(:va_v3_contact_information_service)
+ Flipper.disable(:remove_pciu)
+ end
+
+ describe 'GET /mobile/v0/user', :skip_va_profile_user do
let!(:user) do
sis_user(
first_name: 'GREG',
@@ -25,7 +30,6 @@
end
before(:all) do
- Flipper.disable(:va_v3_contact_information_service)
Flipper.disable(:mobile_lighthouse_letters)
end
@@ -459,7 +463,7 @@
end
end
- describe 'POST /mobile/v0/user/logged-in' do
+ describe 'POST /mobile/v0/user/logged-in', :skip_va_profile_user do
let!(:user) { sis_user }
it 'returns an ok response' do
diff --git a/modules/mobile/spec/requests/mobile/v1/user_spec.rb b/modules/mobile/spec/requests/mobile/v1/user_spec.rb
index e885146f85d..3e32d357572 100644
--- a/modules/mobile/spec/requests/mobile/v1/user_spec.rb
+++ b/modules/mobile/spec/requests/mobile/v1/user_spec.rb
@@ -5,12 +5,16 @@
RSpec.describe 'Mobile::V1::User', type: :request do
include JsonSchemaMatchers
- Flipper.disable(:va_v3_contact_information_service)
let(:contact_information_service) do
VAProfile::ContactInformation::Service
end
- describe 'GET /mobile/v1/user' do
+ before do
+ Flipper.disable(:va_v3_contact_information_service)
+ Flipper.disable(:remove_pciu)
+ end
+
+ describe 'GET /mobile/v1/user', :skip_va_profile_user do
let!(:user) do
sis_user(
first_name: 'GREG',
diff --git a/modules/mobile/spec/services/sync_update_service_spec.rb b/modules/mobile/spec/services/sync_update_service_spec.rb
index 52f6a449305..6d0fc918c79 100644
--- a/modules/mobile/spec/services/sync_update_service_spec.rb
+++ b/modules/mobile/spec/services/sync_update_service_spec.rb
@@ -6,12 +6,14 @@
let(:user) { create(:user, :api_auth) }
let(:service) { Mobile::V0::Profile::SyncUpdateService.new(user) }
- # DO THIS
- describe '#save_and_await_response' do
- before do
- Flipper.disable(:va_v3_contact_information_service)
- end
+ before do
+ allow(Flipper).to receive(:enabled?).with(:mobile_v2_contact_info, instance_of(User)).and_return(false)
+ allow(Flipper).to receive(:enabled?).with(:va_v3_contact_information_service, instance_of(User)).and_return(false)
+ allow(Flipper).to receive(:enabled?).with(:remove_pciu, instance_of(User)).and_return(false)
+ end
+ # DO THIS
+ describe '#save_and_await_response', :skip_va_profile_user do
let(:params) { build(:va_profile_address, vet360_id: user.vet360_id, validation_key: nil) }
context 'when it succeeds after one incomplete status check' do
diff --git a/modules/representation_management/app/serializers/representation_management/power_of_attorney/representative_serializer.rb b/modules/representation_management/app/serializers/representation_management/power_of_attorney/representative_serializer.rb
index c65ce0e5309..439f9a2257c 100644
--- a/modules/representation_management/app/serializers/representation_management/power_of_attorney/representative_serializer.rb
+++ b/modules/representation_management/app/serializers/representation_management/power_of_attorney/representative_serializer.rb
@@ -9,6 +9,10 @@ class RepresentativeSerializer < BaseSerializer
'representative'
end
+ attribute :individual_type do |object|
+ object.user_types.first
+ end
+
attribute :email
attribute :name, &:full_name
attribute :phone, &:phone_number
diff --git a/modules/representation_management/app/swagger/v0/swagger.json b/modules/representation_management/app/swagger/v0/swagger.json
index 510ff941e97..81a514190bf 100644
--- a/modules/representation_management/app/swagger/v0/swagger.json
+++ b/modules/representation_management/app/swagger/v0/swagger.json
@@ -321,25 +321,36 @@
"type": "string",
"description": "Specifies the category of Power of Attorney (POA) representation.",
"enum": [
- "veteran_service_representatives",
- "veteran_service_organizations"
- ]
+ "representative",
+ "organization"
+ ],
+ "example": "representative"
},
"attributes": {
"type": "object",
"properties": {
"type": {
"type": "string",
- "example": "organization",
+ "example": "representative",
"description": "Type of Power of Attorney representation",
"enum": [
"organization",
"representative"
]
},
+ "individual_type": {
+ "type": "string",
+ "description": "The type of individual appointed",
+ "enum": [
+ "attorney",
+ "claim_agents",
+ "veteran_service_officer"
+ ],
+ "example": "attorney"
+ },
"name": {
"type": "string",
- "example": "Veterans Association"
+ "example": "Bob Law"
},
"address_line1": {
"type": "string",
@@ -725,9 +736,7 @@
"PDF Generation"
],
"operationId": "createPdfForm2122",
- "parameters": [
-
- ],
+ "parameters": [],
"responses": {
"200": {
"description": "PDF generated successfully"
@@ -958,9 +967,7 @@
"PDF Generation"
],
"operationId": "createPdfForm2122a",
- "parameters": [
-
- ],
+ "parameters": [],
"responses": {
"200": {
"description": "PDF generated successfully"
@@ -1218,8 +1225,7 @@
{
"type": "object",
"description": "An empty JSON object indicating no Power of Attorney exists.",
- "example": {
- }
+ "example": {}
}
]
}
@@ -1256,9 +1262,7 @@
"Next Steps Email"
],
"operationId": "nextStepsEmail",
- "parameters": [
-
- ],
+ "parameters": [],
"responses": {
"200": {
"description": "Email enqueued",
diff --git a/modules/representation_management/spec/requests/representation_management/v0/power_of_attorney_swagger_spec.rb b/modules/representation_management/spec/requests/representation_management/v0/power_of_attorney_swagger_spec.rb
index 19385ff8949..e31df178881 100644
--- a/modules/representation_management/spec/requests/representation_management/v0/power_of_attorney_swagger_spec.rb
+++ b/modules/representation_management/spec/requests/representation_management/v0/power_of_attorney_swagger_spec.rb
@@ -25,7 +25,7 @@
before do
lh_response = {
'data' => {
- 'type' => 'organization',
+ 'type' => 'individual',
'attributes' => {
'code' => 'abc'
}
diff --git a/modules/representation_management/spec/serializers/power_of_attorney/representative_serializer_spec.rb b/modules/representation_management/spec/serializers/power_of_attorney/representative_serializer_spec.rb
index e264677555b..81c2092837e 100644
--- a/modules/representation_management/spec/serializers/power_of_attorney/representative_serializer_spec.rb
+++ b/modules/representation_management/spec/serializers/power_of_attorney/representative_serializer_spec.rb
@@ -27,4 +27,8 @@
it 'includes :phone' do
expect(attributes['phone']).to eq object.phone_number
end
+
+ it 'includes :individual_type' do
+ expect(attributes['individual_type']).to eq 'attorney'
+ end
end
diff --git a/modules/representation_management/spec/support/rswag_config.rb b/modules/representation_management/spec/support/rswag_config.rb
index 777a7e8cfc9..9e2e6af626b 100644
--- a/modules/representation_management/spec/support/rswag_config.rb
+++ b/modules/representation_management/spec/support/rswag_config.rb
@@ -121,7 +121,7 @@ def power_of_attorney_response
type: {
type: :string,
description: 'Specifies the category of Power of Attorney (POA) representation.',
- enum: %w[veteran_service_representatives veteran_service_organizations]
+ enum: %w[representative organization]
},
attributes: power_of_attorney_attributes
}
@@ -136,9 +136,15 @@ def power_of_attorney_attributes
properties: {
type: {
type: :string,
- example: 'organization',
+ example: 'representative',
description: 'Type of Power of Attorney representation',
enum: %w[organization representative]
+ },
+ individual_type: {
+ type: :string,
+ description: 'The type of individual appointed',
+ enum: %w[attorney claim_agents veteran_service_officer],
+ example: 'attorney'
}
}.merge(power_of_attorney_detailed_attributes),
required: %w[type name address_line1 city state_code zip_code]
@@ -249,7 +255,7 @@ def address_properties
def power_of_attorney_detailed_attributes
{
- name: { type: :string, example: 'Veterans Association' },
+ name: { type: :string, example: 'Bob Law' },
address_line1: { type: :string, example: '1234 Freedom Blvd' },
address_line2: { type: :string, example: 'Suite 200' },
address_line3: { type: :string, example: 'Building 3' },
diff --git a/modules/simple_forms_api/app/controllers/simple_forms_api/v1/scanned_form_uploads_controller.rb b/modules/simple_forms_api/app/controllers/simple_forms_api/v1/scanned_form_uploads_controller.rb
index 3d5845dc744..908f05035e0 100644
--- a/modules/simple_forms_api/app/controllers/simple_forms_api/v1/scanned_form_uploads_controller.rb
+++ b/modules/simple_forms_api/app/controllers/simple_forms_api/v1/scanned_form_uploads_controller.rb
@@ -8,6 +8,8 @@ module V1
class ScannedFormUploadsController < ApplicationController
def submit
Datadog::Tracing.active_trace&.set_tag('form_id', params[:form_number])
+ check_for_changes
+
render json: upload_response
end
@@ -34,7 +36,12 @@ def upload_response
stamper.stamp_pdf
metadata = validated_metadata
status, confirmation_number = upload_pdf(file_path, metadata)
+ file_size = File.size(file_path).to_f / (2**20)
+ Rails.logger.info(
+ 'Simple forms api - scanned form uploaded',
+ { form_number: params[:form_number], status:, confirmation_number:, file_size: }
+ )
{ confirmation_number:, status: }
end
@@ -44,13 +51,11 @@ def find_attachment_path(confirmation_code)
def validated_metadata
raw_metadata = {
- 'veteranFirstName' => @current_user.first_name,
- 'veteranLastName' => @current_user.last_name,
- 'fileNumber' => params.dig(:options, :ssn) ||
- params.dig(:options, :va_file_number) ||
- @current_user.ssn,
- 'zipCode' => params.dig(:options, :zip_code) ||
- @current_user.address[:postal_code],
+ 'veteranFirstName' => params.dig(:form_data, :full_name, :first),
+ 'veteranLastName' => params.dig(:form_data, :full_name, :last),
+ 'fileNumber' => params.dig(:form_data, :id_number, :ssn) ||
+ params.dig(:form_data, :id_number, :va_file_number),
+ 'zipCode' => params.dig(:form_data, :postal_code),
'source' => 'VA Platform Digital Forms',
'docType' => params[:form_number],
'businessLine' => 'CMP'
@@ -98,6 +103,16 @@ def perform_pdf_upload(location, file_path, metadata)
upload_url: location
)
end
+
+ def check_for_changes
+ in_progress_form = InProgressForm.form_for_user('FORM-UPLOAD-FLOW', @current_user)
+ if in_progress_form
+ prefill_data_service = SimpleFormsApi::PrefillDataService.new(prefill_data: in_progress_form.form_data,
+ form_data: params[:form_data],
+ form_id: params[:form_number])
+ prefill_data_service.check_for_changes
+ end
+ end
end
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 6297706941d..155f08aa8e9 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
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'ddtrace'
+require 'datadog'
require 'simple_forms_api_submission/metadata_validator'
require 'lgy/service'
require 'lighthouse/benefits_intake/service'
@@ -129,11 +129,11 @@ def handle264555
if Flipper.enabled?(:simple_forms_email_confirmations)
case status
when 'VALIDATED', 'ACCEPTED'
- send_sahsha_email(parsed_form_data, reference_number, :confirmation)
+ send_sahsha_email(parsed_form_data, :confirmation, reference_number)
when 'REJECTED'
- send_sahsha_email(parsed_form_data, reference_number, :rejected)
+ send_sahsha_email(parsed_form_data, :rejected)
when 'DUPLICATE'
- send_sahsha_email(parsed_form_data, reference_number, :duplicate)
+ send_sahsha_email(parsed_form_data, :duplicate)
end
end
@@ -339,7 +339,7 @@ def send_intent_received_email(parsed_form_data, confirmation_number, expiration
notification_email.send
end
- def send_sahsha_email(parsed_form_data, confirmation_number, notification_type)
+ def send_sahsha_email(parsed_form_data, notification_type, confirmation_number = nil)
config = {
form_data: parsed_form_data,
form_number: 'vba_26_4555',
diff --git a/modules/simple_forms_api/app/form_mappings/vba_21_4140.json.erb b/modules/simple_forms_api/app/form_mappings/vba_21_4140.json.erb
index 42d98777196..38c93bbdcbf 100644
--- a/modules/simple_forms_api/app/form_mappings/vba_21_4140.json.erb
+++ b/modules/simple_forms_api/app/form_mappings/vba_21_4140.json.erb
@@ -1,61 +1,67 @@
{
- "F[0].Page_1[0].Station_Address[0]": "<%= data.dig('Station_Address') %>",
- "F[0].Page_1[0].VeteranLastName[0]": "<%= data.dig('VeteranLastName') %>",
- "F[0].Page_1[0].VeteranMiddleInitial1[0]": "<%= data.dig('VeteranMiddleInitial1') %>",
- "F[0].Page_1[0].VeteranFirstName[0]": "<%= data.dig('VeteranFirstName') %>",
- "F[0].Page_1[0].DOByear[0]": "<%= data.dig('DOByear') %>",
- "F[0].Page_1[0].DOBday[0]": "<%= data.dig('DOBday') %>",
- "F[0].Page_1[0].DOBmonth[0]": "<%= data.dig('DOBmonth') %>",
- "F[0].Page_1[0].VAFileNumber[0]": "<%= data.dig('VAFileNumber') %>",
- "F[0].Page_1[0].Veterans_Social_SecurityNumber_LastFourNumbers[0]": "<%= data.dig('Veterans_Social_SecurityNumber_LastFourNumbers') %>",
- "F[0].Page_1[0].Veterans_Social_SecurityNumber_SecondTwoNumbers[0]": "<%= data.dig('Veterans_Social_SecurityNumber_SecondTwoNumbers') %>",
- "F[0].Page_1[0].Veterans_Social_SecurityNumber_FirstThreeNumbers[0]": "<%= data.dig('Veterans_Social_SecurityNumber_FirstThreeNumbers') %>",
- "F[0].Page_1[0].VeteransServiceNumber[0]": "<%= data.dig('VeteransServiceNumber') %>",
- "F[0].Page_1[0].CurrentMailingAddress_ZIPOrPostalCode_LastFourNumbers[0]": "<%= data.dig('CurrentMailingAddress_ZIPOrPostalCode_LastFourNumbers') %>",
- "F[0].Page_1[0].CurrentMailingAddress_ZIPOrPostalCode_FirstFiveNumbers[0]": "<%= data.dig('CurrentMailingAddress_ZIPOrPostalCode_FirstFiveNumbers') %>",
- "F[0].Page_1[0].CurrentMailingAddress_Country[0]": "<%= data.dig('CurrentMailingAddress_Country') %>",
- "F[0].Page_1[0].CurrentMailingAddress_StateOrProvince[0]": "<%= data.dig('CurrentMailingAddress_StateOrProvince') %>",
- "F[0].Page_1[0].CurrentMailingAddress_City[0]": "<%= data.dig('CurrentMailingAddress_City') %>",
- "F[0].Page_1[0].CurrentMailingAddress_ApartmentOrUnitNumber[0]": "<%= data.dig('CurrentMailingAddress_ApartmentOrUnitNumber') %>",
- "F[0].Page_1[0].CurrentMailingAddress_NumberAndStreet[0]": "<%= data.dig('CurrentMailingAddress_NumberAndStreet') %>",
- "F[0].Page_1[0].E-Mail_Address[0]": "<%= data.dig('E-Mail_Address') %>",
- "F[0].Page_1[0].Type_Of_Work[0]": "<%= data.dig('Type_Of_Work') %>",
- "F[0].Page_1[0].Time_Lost_From_Illness[0]": "<%= data.dig('Time_Lost_From_Illness') %>",
- "F[0].Page_1[0].Name_And_Address_Of_Employer[0]": "<%= data.dig('Name_And_Address_Of_Employer') %>",
- "F[0].Page_1[0].PrimaryTelephoneNumber[0]": "<%= data.dig('PrimaryTelephoneNumber') %>",
- "F[0].Page_1[0].AlternateTelephoneNumber[0]": "<%= data.dig('AlternateTelephoneNumber') %>",
- "F[0].Page_1[0].RadioButtonList[0]": "<%= data.dig('RadioButtonList') %>",
- "F[0].Page_1[0].Date_Mailed[0]": "<%= data.dig('Date_Mailed') %>",
- "F[0].Page_1[0].Hours_Per_Week[0]": "<%= data.dig('Hours_Per_Week') %>",
- "F[0].Page_1[0].Date_Of_Employment_From[0]": "<%= data.dig('Date_Of_Employment_From') %>",
- "F[0].Page_1[0].Date_Of_Employment_To[0]": "<%= data.dig('Date_Of_Employment_To') %>",
- "F[0].Page_1[0].Gross_Earnings_Per_Month[0]": "<%= data.dig('Gross_Earnings_Per_Month') %>",
- "F[0].#subform[1].DateSigned[0]": "<%= data.dig('DateSigned') %>",
- "F[0].#subform[1].SignatureField1[0]": "<%= data.dig('SignatureField1') %>",
- "F[0].#subform[1].Veterans_Social_SecurityNumber_LastFourNumbers[0]": "<%= data.dig('Veterans_Social_SecurityNumber_LastFourNumbers') %>",
- "F[0].#subform[1].Veterans_Social_SecurityNumber_SecondTwoNumbers[0]": "<%= data.dig('Veterans_Social_SecurityNumber_SecondTwoNumbers') %>",
- "F[0].#subform[1].Veterans_Social_SecurityNumber_FirstThreeNumbers[0]": "<%= data.dig('Veterans_Social_SecurityNumber_FirstThreeNumbers') %>",
- "F[0].#subform[1].Gross_Earnings_Per_Month[0]": "<%= data.dig('Gross_Earnings_Per_Month') %>",
- "F[0].#subform[1].Type_Of_Work[0]": "<%= data.dig('Type_Of_Work') %>",
- "F[0].#subform[1].Time_Lost_From_Illness[0]": "<%= data.dig('Time_Lost_From_Illness') %>",
- "F[0].#subform[1].Name_And_Address_Of_Employer[0]": "<%= data.dig('Name_And_Address_Of_Employer') %>",
- "F[0].#subform[1].Hours_Per_Week[0]": "<%= data.dig('Hours_Per_Week') %>",
- "F[0].#subform[1].Date_Of_Employment_From[0]": "<%= data.dig('Date_Of_Employment_From') %>",
- "F[0].#subform[1].Date_Of_Employment_To[0]": "<%= data.dig('Date_Of_Employment_To') %>",
- "F[0].#subform[1].Gross_Earnings_Per_Month[1]": "<%= data.dig('Gross_Earnings_Per_Month') %>",
- "F[0].#subform[1].Date_Of_Employment_To[1]": "<%= data.dig('Date_Of_Employment_To') %>",
- "F[0].#subform[1].Date_Of_Employment_From[1]": "<%= data.dig('Date_Of_Employment_From') %>",
- "F[0].#subform[1].Hours_Per_Week[1]": "<%= data.dig('Hours_Per_Week') %>",
- "F[0].#subform[1].Name_And_Address_Of_Employer[1]": "<%= data.dig('Name_And_Address_Of_Employer') %>",
- "F[0].#subform[1].Time_Lost_From_Illness[1]": "<%= data.dig('Time_Lost_From_Illness') %>",
- "F[0].#subform[1].Type_Of_Work[1]": "<%= data.dig('Type_Of_Work') %>",
- "F[0].#subform[1].Type_Of_Work[2]": "<%= data.dig('Type_Of_Work') %>",
- "F[0].#subform[1].Time_Lost_From_Illness[2]": "<%= data.dig('Time_Lost_From_Illness') %>",
- "F[0].#subform[1].Name_And_Address_Of_Employer[2]": "<%= data.dig('Name_And_Address_Of_Employer') %>",
- "F[0].#subform[1].Hours_Per_Week[2]": "<%= data.dig('Hours_Per_Week') %>",
- "F[0].#subform[1].Date_Of_Employment_From[2]": "<%= data.dig('Date_Of_Employment_From') %>",
- "F[0].#subform[1].Date_Of_Employment_To[2]": "<%= data.dig('Date_Of_Employment_To') %>",
- "F[0].#subform[1].Gross_Earnings_Per_Month[2]": "<%= data.dig('Gross_Earnings_Per_Month') %>",
- "F[0].#subform[1].SignatureField1[1]": "<%= data.dig('SignatureField1') %>",
- "F[0].#subform[1].DateSigned[1]": "<%= data.dig('DateSigned') %>"
+ "F[0].Page_1[0].Station_Address[0]": "<%= form.data.dig('Station_Address') %>",
+
+ <%# Page 1 %>
+
+ <%# Section 1: Veteran's Identification Information %>
+
+ <%# 1. Name of Veteran %>
+ "F[0].Page_1[0].VeteranFirstName[0]": "<%= form.first_name %>",
+ "F[0].Page_1[0].VeteranMiddleInitial1[0]": "<%= form.middle_initial %>",
+ "F[0].Page_1[0].VeteranLastName[0]": "<%= form.last_name %>",
+ "F[0].Page_1[0].DOByear[0]": "<%= form.data.dig('DOByear') %>",
+ "F[0].Page_1[0].DOBday[0]": "<%= form.data.dig('DOBday') %>",
+ "F[0].Page_1[0].DOBmonth[0]": "<%= form.data.dig('DOBmonth') %>",
+ "F[0].Page_1[0].VAFileNumber[0]": "<%= form.data.dig('VAFileNumber') %>",
+ "F[0].Page_1[0].Veterans_Social_SecurityNumber_LastFourNumbers[0]": "<%= form.data.dig('Veterans_Social_SecurityNumber_LastFourNumbers') %>",
+ "F[0].Page_1[0].Veterans_Social_SecurityNumber_SecondTwoNumbers[0]": "<%= form.data.dig('Veterans_Social_SecurityNumber_SecondTwoNumbers') %>",
+ "F[0].Page_1[0].Veterans_Social_SecurityNumber_FirstThreeNumbers[0]": "<%= form.data.dig('Veterans_Social_SecurityNumber_FirstThreeNumbers') %>",
+ "F[0].Page_1[0].VeteransServiceNumber[0]": "<%= form.data.dig('VeteransServiceNumber') %>",
+ "F[0].Page_1[0].CurrentMailingAddress_ZIPOrPostalCode_LastFourNumbers[0]": "<%= form.data.dig('CurrentMailingAddress_ZIPOrPostalCode_LastFourNumbers') %>",
+ "F[0].Page_1[0].CurrentMailingAddress_ZIPOrPostalCode_FirstFiveNumbers[0]": "<%= form.data.dig('CurrentMailingAddress_ZIPOrPostalCode_FirstFiveNumbers') %>",
+ "F[0].Page_1[0].CurrentMailingAddress_Country[0]": "<%= form.data.dig('CurrentMailingAddress_Country') %>",
+ "F[0].Page_1[0].CurrentMailingAddress_StateOrProvince[0]": "<%= form.data.dig('CurrentMailingAddress_StateOrProvince') %>",
+ "F[0].Page_1[0].CurrentMailingAddress_City[0]": "<%= form.data.dig('CurrentMailingAddress_City') %>",
+ "F[0].Page_1[0].CurrentMailingAddress_ApartmentOrUnitNumber[0]": "<%= form.data.dig('CurrentMailingAddress_ApartmentOrUnitNumber') %>",
+ "F[0].Page_1[0].CurrentMailingAddress_NumberAndStreet[0]": "<%= form.data.dig('CurrentMailingAddress_NumberAndStreet') %>",
+ "F[0].Page_1[0].E-Mail_Address[0]": "<%= form.data.dig('E-Mail_Address') %>",
+ "F[0].Page_1[0].Type_Of_Work[0]": "<%= form.data.dig('Type_Of_Work') %>",
+ "F[0].Page_1[0].Time_Lost_From_Illness[0]": "<%= form.data.dig('Time_Lost_From_Illness') %>",
+ "F[0].Page_1[0].Name_And_Address_Of_Employer[0]": "<%= form.data.dig('Name_And_Address_Of_Employer') %>",
+ "F[0].Page_1[0].PrimaryTelephoneNumber[0]": "<%= form.data.dig('PrimaryTelephoneNumber') %>",
+ "F[0].Page_1[0].AlternateTelephoneNumber[0]": "<%= form.data.dig('AlternateTelephoneNumber') %>",
+ "F[0].Page_1[0].RadioButtonList[0]": "<%= form.data.dig('RadioButtonList') %>",
+ "F[0].Page_1[0].Date_Mailed[0]": "<%= form.data.dig('Date_Mailed') %>",
+ "F[0].Page_1[0].Hours_Per_Week[0]": "<%= form.data.dig('Hours_Per_Week') %>",
+ "F[0].Page_1[0].Date_Of_Employment_From[0]": "<%= form.data.dig('Date_Of_Employment_From') %>",
+ "F[0].Page_1[0].Date_Of_Employment_To[0]": "<%= form.data.dig('Date_Of_Employment_To') %>",
+ "F[0].Page_1[0].Gross_Earnings_Per_Month[0]": "<%= form.data.dig('Gross_Earnings_Per_Month') %>",
+ "F[0].#subform[1].DateSigned[0]": "<%= form.data.dig('DateSigned') %>",
+ "F[0].#subform[1].SignatureField1[0]": "<%= form.data.dig('SignatureField1') %>",
+ "F[0].#subform[1].Veterans_Social_SecurityNumber_LastFourNumbers[0]": "<%= form.data.dig('Veterans_Social_SecurityNumber_LastFourNumbers') %>",
+ "F[0].#subform[1].Veterans_Social_SecurityNumber_SecondTwoNumbers[0]": "<%= form.data.dig('Veterans_Social_SecurityNumber_SecondTwoNumbers') %>",
+ "F[0].#subform[1].Veterans_Social_SecurityNumber_FirstThreeNumbers[0]": "<%= form.data.dig('Veterans_Social_SecurityNumber_FirstThreeNumbers') %>",
+ "F[0].#subform[1].Gross_Earnings_Per_Month[0]": "<%= form.data.dig('Gross_Earnings_Per_Month') %>",
+ "F[0].#subform[1].Type_Of_Work[0]": "<%= form.data.dig('Type_Of_Work') %>",
+ "F[0].#subform[1].Time_Lost_From_Illness[0]": "<%= form.data.dig('Time_Lost_From_Illness') %>",
+ "F[0].#subform[1].Name_And_Address_Of_Employer[0]": "<%= form.data.dig('Name_And_Address_Of_Employer') %>",
+ "F[0].#subform[1].Hours_Per_Week[0]": "<%= form.data.dig('Hours_Per_Week') %>",
+ "F[0].#subform[1].Date_Of_Employment_From[0]": "<%= form.data.dig('Date_Of_Employment_From') %>",
+ "F[0].#subform[1].Date_Of_Employment_To[0]": "<%= form.data.dig('Date_Of_Employment_To') %>",
+ "F[0].#subform[1].Gross_Earnings_Per_Month[1]": "<%= form.data.dig('Gross_Earnings_Per_Month') %>",
+ "F[0].#subform[1].Date_Of_Employment_To[1]": "<%= form.data.dig('Date_Of_Employment_To') %>",
+ "F[0].#subform[1].Date_Of_Employment_From[1]": "<%= form.data.dig('Date_Of_Employment_From') %>",
+ "F[0].#subform[1].Hours_Per_Week[1]": "<%= form.data.dig('Hours_Per_Week') %>",
+ "F[0].#subform[1].Name_And_Address_Of_Employer[1]": "<%= form.data.dig('Name_And_Address_Of_Employer') %>",
+ "F[0].#subform[1].Time_Lost_From_Illness[1]": "<%= form.data.dig('Time_Lost_From_Illness') %>",
+ "F[0].#subform[1].Type_Of_Work[1]": "<%= form.data.dig('Type_Of_Work') %>",
+ "F[0].#subform[1].Type_Of_Work[2]": "<%= form.data.dig('Type_Of_Work') %>",
+ "F[0].#subform[1].Time_Lost_From_Illness[2]": "<%= form.data.dig('Time_Lost_From_Illness') %>",
+ "F[0].#subform[1].Name_And_Address_Of_Employer[2]": "<%= form.data.dig('Name_And_Address_Of_Employer') %>",
+ "F[0].#subform[1].Hours_Per_Week[2]": "<%= form.data.dig('Hours_Per_Week') %>",
+ "F[0].#subform[1].Date_Of_Employment_From[2]": "<%= form.data.dig('Date_Of_Employment_From') %>",
+ "F[0].#subform[1].Date_Of_Employment_To[2]": "<%= form.data.dig('Date_Of_Employment_To') %>",
+ "F[0].#subform[1].Gross_Earnings_Per_Month[2]": "<%= form.data.dig('Gross_Earnings_Per_Month') %>",
+ "F[0].#subform[1].SignatureField1[1]": "<%= form.data.dig('SignatureField1') %>",
+ "F[0].#subform[1].DateSigned[1]": "<%= form.data.dig('DateSigned') %>"
}
diff --git a/modules/simple_forms_api/app/models/simple_forms_api/vba_21_4140.rb b/modules/simple_forms_api/app/models/simple_forms_api/vba_21_4140.rb
index b561f722623..9df58196cb2 100644
--- a/modules/simple_forms_api/app/models/simple_forms_api/vba_21_4140.rb
+++ b/modules/simple_forms_api/app/models/simple_forms_api/vba_21_4140.rb
@@ -6,20 +6,36 @@ def desired_stamps
[]
end
+ def first_name
+ data.dig('full_name', 'first')&.[](0..11)
+ end
+
+ def last_name
+ data.dig('full_name', 'last')&.[](0..17)
+ end
+
def metadata
{
- 'veteranFirstName' => @data.dig('full_name', 'first'),
- 'veteranLastName' => @data.dig('full_name', 'last'),
- 'fileNumber' => @data['va_file_number'].presence || @data['ssn'],
- 'zipCode' => @data.dig('address', 'postal_code'),
+ 'veteranFirstName' => data.dig('full_name', 'first'),
+ 'veteranLastName' => data.dig('full_name', 'last'),
+ 'fileNumber' => data['va_file_number'].presence || data['ssn'],
+ 'zipCode' => data.dig('address', 'postal_code'),
'source' => 'VA Platform Digital Forms',
- 'docType' => @data['form_number'],
+ 'docType' => data['form_number'],
'businessLine' => 'CMP'
}
end
+ def middle_initial
+ data.dig('full_name', 'middle')&.[](0)
+ end
+
def submission_date_stamps(_timestamp)
[]
end
+
+ def zip_code_is_us_based
+ data.dig('address', 'country') == 'USA'
+ end
end
end
diff --git a/modules/simple_forms_api/app/services/simple_forms_api/notification_email.rb b/modules/simple_forms_api/app/services/simple_forms_api/notification_email.rb
index 016862eadf7..0d274317b9a 100644
--- a/modules/simple_forms_api/app/services/simple_forms_api/notification_email.rb
+++ b/modules/simple_forms_api/app/services/simple_forms_api/notification_email.rb
@@ -100,10 +100,12 @@ def send(at: nil)
private
def check_missing_keys(config)
- missing_keys = %i[form_data form_number confirmation_number date_submitted].select { |key| config[key].nil? }
- if config[:form_number] == 'vba_21_0966_intent_api' && config[:expiration_date].nil?
- missing_keys << :expiration_date
- end
+ all_keys = %i[form_data form_number date_submitted]
+ all_keys << :confirmation_number if needs_confirmation_number?(config)
+ all_keys << :expiration_date if config[:form_number] == 'vba_21_0966_intent_api'
+
+ missing_keys = all_keys.select { |key| config[key].nil? || config[key].to_s.strip.empty? }
+
if missing_keys.any?
StatsD.increment('silent_failure', tags: statsd_tags) if error_notification?
raise ArgumentError, "Missing keys: #{missing_keys.join(', ')}"
@@ -280,6 +282,7 @@ def get_personalization(first_name)
default_personalization(first_name)
end
personalization.except!('lighthouse_updated_at') unless lighthouse_updated_at
+ personalization.except!('confirmation_number') unless confirmation_number
personalization
end
@@ -431,5 +434,9 @@ def statsd_tags
def error_notification?
notification_type == :error
end
+
+ def needs_confirmation_number?(config)
+ config[:form_number] != 'vba_26_4555' && %w[REJECTED DUPLICATE].exclude?(config[:notification_type])
+ end
end
end
diff --git a/modules/simple_forms_api/app/services/simple_forms_api/prefill_data_service.rb b/modules/simple_forms_api/app/services/simple_forms_api/prefill_data_service.rb
new file mode 100644
index 00000000000..f81f9de0b78
--- /dev/null
+++ b/modules/simple_forms_api/app/services/simple_forms_api/prefill_data_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module SimpleFormsApi
+ class PrefillDataService
+ attr_reader :prefill_data, :form_data, :form_id
+
+ def initialize(prefill_data:, form_data:, form_id:)
+ @prefill_data = JSON.parse(prefill_data)
+ @form_data = form_data
+ @form_id = form_id
+ end
+
+ def check_for_changes
+ changed_fields = form_upload_field_paths.map do |key, value|
+ key if prefill_data.dig(*value[:prefill_path]) != form_data.dig(*value[:form_data_path])
+ end.compact
+
+ changed_fields.each do |field|
+ Rails.logger.info('Simple forms api - Form Upload Flow changed data', { field:, form_id: })
+ end
+ end
+
+ private
+
+ def form_upload_field_paths
+ {
+ first_name: { prefill_path: %w[full_name first], form_data_path: %w[full_name first] },
+ last_name: { prefill_path: %w[full_name last], form_data_path: %w[full_name last] },
+ postal_code: { prefill_path: %w[address postal_code], form_data_path: %w[postal_code] },
+ ssn: { prefill_path: %w[veteran ssn], form_data_path: %w[id_number ssn] },
+ email: { prefill_path: %w[email], form_data_path: %w[email] }
+ }
+ end
+ end
+end
diff --git a/modules/simple_forms_api/spec/fixtures/form_json/vba_21_4140.json b/modules/simple_forms_api/spec/fixtures/form_json/vba_21_4140.json
index 2636cf98028..c045b8e25b5 100644
--- a/modules/simple_forms_api/spec/fixtures/form_json/vba_21_4140.json
+++ b/modules/simple_forms_api/spec/fixtures/form_json/vba_21_4140.json
@@ -17,10 +17,9 @@
"postal_code": "90210"
},
"full_name": {
- "first": "Example",
+ "first": "Rumpelstiltskin",
"middle": "Test",
- "last": "Surnam/",
- "suffix": "Jr."
+ "last": "Mephistopheles-Reinhardt"
},
"date_of_birth": "1979-02-27",
"employers": [
diff --git a/modules/simple_forms_api/spec/models/vba_20_10207_spec.rb b/modules/simple_forms_api/spec/models/vba_20_10207_spec.rb
index 479e0ab5860..0b5c516af6d 100644
--- a/modules/simple_forms_api/spec/models/vba_20_10207_spec.rb
+++ b/modules/simple_forms_api/spec/models/vba_20_10207_spec.rb
@@ -1,51 +1,10 @@
# frozen_string_literal: true
require 'rails_helper'
+require_relative '../support/shared_examples_for_base_form'
RSpec.describe SimpleFormsApi::VBA2010207 do
- describe 'zip_code_is_us_based' do
- subject(:zip_code_is_us_based) { described_class.new(data).zip_code_is_us_based }
-
- context 'veteran address is present and in US' do
- let(:data) { { 'veteran_mailing_address' => { 'country' => 'USA' } } }
-
- it 'returns true' do
- expect(zip_code_is_us_based).to eq(true)
- end
- end
-
- context 'veteran address is present and not in US' do
- let(:data) { { 'veteran_mailing_address' => { 'country' => 'Canada' } } }
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
-
- context 'non-veteran address is present and in US' do
- let(:data) { { 'non_veteran_mailing_address' => { 'country' => 'USA' } } }
-
- it 'returns true' do
- expect(zip_code_is_us_based).to eq(true)
- end
- end
-
- context 'non-veteran address is present and not in US' do
- let(:data) { { 'non_veteran_mailing_address' => { 'country' => 'Canada' } } }
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
-
- context 'no valid address is given' do
- let(:data) { {} }
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
- end
+ it_behaves_like 'zip_code_is_us_based', %w[veteran_mailing_address non_veteran_mailing_address]
describe 'requester_signature' do
statement_of_truth_signature = 'John Veteran'
diff --git a/modules/simple_forms_api/spec/models/vba_21_0845_spec.rb b/modules/simple_forms_api/spec/models/vba_21_0845_spec.rb
index a722ae6a026..e540f4133ee 100644
--- a/modules/simple_forms_api/spec/models/vba_21_0845_spec.rb
+++ b/modules/simple_forms_api/spec/models/vba_21_0845_spec.rb
@@ -1,69 +1,8 @@
# frozen_string_literal: true
require 'rails_helper'
+require_relative '../support/shared_examples_for_base_form'
RSpec.describe SimpleFormsApi::VBA210845 do
- describe 'zip_code_is_us_based' do
- subject(:zip_code_is_us_based) { described_class.new(data).zip_code_is_us_based }
-
- context 'authorizer address is present and in US' do
- let(:data) { { 'authorizer_address' => { 'country' => 'USA' } } }
-
- it 'returns true' do
- expect(zip_code_is_us_based).to eq(true)
- end
- end
-
- context 'authorizer address is present and not in US' do
- let(:data) { { 'authorizer_address' => { 'country' => 'Canada' } } }
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
-
- context 'person is present and in US' do
- let(:data) { { 'person_address' => { 'country' => 'USA' } } }
-
- it 'returns true' do
- expect(zip_code_is_us_based).to eq(true)
- end
- end
-
- context 'person is present and not in US' do
- let(:data) { { 'person_address' => { 'country' => 'Canada' } } }
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
-
- context 'organization is present and in US' do
- let(:data) do
- { 'organization_address' => { 'country' => 'USA' } }
- end
-
- it 'returns true' do
- expect(zip_code_is_us_based).to eq(true)
- end
- end
-
- context 'organization is present and not in US' do
- let(:data) do
- { 'organization_address' => { 'country' => 'Canada' } }
- end
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
-
- context 'no valid address is given' do
- let(:data) { {} }
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
- end
+ it_behaves_like 'zip_code_is_us_based', %w[authorizer_address person_address organization_address]
end
diff --git a/modules/simple_forms_api/spec/models/vba_21_0966_spec.rb b/modules/simple_forms_api/spec/models/vba_21_0966_spec.rb
index 50eac06d0f9..f3fff43a0e6 100644
--- a/modules/simple_forms_api/spec/models/vba_21_0966_spec.rb
+++ b/modules/simple_forms_api/spec/models/vba_21_0966_spec.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
require 'rails_helper'
+require_relative '../support/shared_examples_for_base_form'
RSpec.describe SimpleFormsApi::VBA210966 do
+ it_behaves_like 'zip_code_is_us_based', %w[veteran_mailing_address surviving_dependent_mailing_address]
+
describe 'populate_veteran_data' do
context 'data does not already have what it needs' do
let(:expected_first_name) { 'Rory' }
@@ -50,48 +53,4 @@
end
end
end
-
- describe 'zip_code_is_us_based' do
- subject(:zip_code_is_us_based) { described_class.new(data).zip_code_is_us_based }
-
- context 'veteran address is present and in US' do
- let(:data) { { 'veteran_mailing_address' => { 'country' => 'USA' } } }
-
- it 'returns true' do
- expect(zip_code_is_us_based).to eq(true)
- end
- end
-
- context 'veteran address is present and not in US' do
- let(:data) { { 'veteran_mailing_address' => { 'country' => 'Canada' } } }
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
-
- context 'surviving dependent is present and in US' do
- let(:data) { { 'surviving_dependent_mailing_address' => { 'country' => 'USA' } } }
-
- it 'returns true' do
- expect(zip_code_is_us_based).to eq(true)
- end
- end
-
- context 'surviving dependent is present and not in US' do
- let(:data) { { 'surviving_dependent_mailing_address' => { 'country' => 'Canada' } } }
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
-
- context 'no valid address is given' do
- let(:data) { {} }
-
- it 'returns false' do
- expect(zip_code_is_us_based).to eq(false)
- end
- end
- end
end
diff --git a/modules/simple_forms_api/spec/models/vba_21_4140_spec.rb b/modules/simple_forms_api/spec/models/vba_21_4140_spec.rb
index fd1ae0dd716..06471672445 100644
--- a/modules/simple_forms_api/spec/models/vba_21_4140_spec.rb
+++ b/modules/simple_forms_api/spec/models/vba_21_4140_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'rails_helper'
+require_relative '../support/shared_examples_for_base_form'
RSpec.describe SimpleFormsApi::VBA214140 do
subject(:form) { described_class.new(data) }
@@ -10,12 +11,32 @@
end
let(:data) { JSON.parse(fixture_path.read) }
+ it_behaves_like 'zip_code_is_us_based', %w[address]
+
describe '#data' do
subject { form.data }
it { is_expected.to match(data) }
end
+ describe '#first_name' do
+ subject { form.first_name }
+
+ it('is limited to twelve characters') do
+ expect(data.dig('full_name', 'first').length).to be > 12
+ expect(subject.length).to eq 12
+ end
+ end
+
+ describe '#last_name' do
+ subject { form.last_name }
+
+ it('is limited to eighteen characters') do
+ expect(data.dig('full_name', 'last').length).to be > 18
+ expect(subject.length).to eq 18
+ end
+ end
+
describe '#metadata' do
subject { form.metadata }
@@ -34,6 +55,15 @@
end
end
+ describe '#middle_initial' do
+ subject { form.middle_initial }
+
+ it('is limited to one character') do
+ expect(data.dig('full_name', 'middle').length).to be > 1
+ expect(subject.length).to eq 1
+ end
+ end
+
describe '#signature_date' do
subject { form.signature_date }
diff --git a/modules/simple_forms_api/spec/models/vba_40_0247_spec.rb b/modules/simple_forms_api/spec/models/vba_40_0247_spec.rb
index 4d1707420c0..003f87c52d5 100644
--- a/modules/simple_forms_api/spec/models/vba_40_0247_spec.rb
+++ b/modules/simple_forms_api/spec/models/vba_40_0247_spec.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
require 'rails_helper'
+require_relative '../support/shared_examples_for_base_form'
+
+RSpec.describe SimpleFormsApi::VBA400247 do
+ it_behaves_like 'zip_code_is_us_based', %w[applicant_address]
-RSpec.describe 'SimpleFormsApi::VBA400247' do
describe 'handle_attachments' do
it 'saves the combined pdf' do
original_pdf = double('HexaPDF::Document')
diff --git a/modules/simple_forms_api/spec/requests/simple_forms_api/v1/scanned_form_uploads_spec.rb b/modules/simple_forms_api/spec/requests/simple_forms_api/v1/scanned_form_uploads_spec.rb
index 5e2d3ff051f..430a2ddf496 100644
--- a/modules/simple_forms_api/spec/requests/simple_forms_api/v1/scanned_form_uploads_spec.rb
+++ b/modules/simple_forms_api/spec/requests/simple_forms_api/v1/scanned_form_uploads_spec.rb
@@ -10,6 +10,7 @@
end
describe '#submit' do
+ let(:form_number) { '21-0779' }
let(:metadata_file) { "#{file_seed}.SimpleFormsApi.metadata.json" }
let(:file_seed) { 'tmp/some-unique-simple-forms-file-seed' }
let(:random_string) { 'some-unique-simple-forms-file-seed' }
@@ -17,6 +18,19 @@
let(:pdf_stamper) { double(stamp_pdf: nil) }
let(:confirmation_code) { 'a-random-guid' }
let(:attachment) { double }
+ let(:params) do
+ { form_number:, confirmation_code:, form_data: {
+ full_name: {
+ first: 'fake-first-name',
+ last: 'fake-last-name'
+ },
+ postal_code: '12345',
+ id_number: {
+ ssn: '444444444'
+ },
+ email: 'fake-email'
+ } }
+ end
before do
VCR.insert_cassette('lighthouse/benefits_intake/200_lighthouse_intake_upload_location')
@@ -40,7 +54,7 @@
it 'makes the request' do
expect(PersistentAttachment).to receive(:find_by).with(guid: confirmation_code).and_return(attachment)
- post '/simple_forms_api/v1/submit_scanned_form', params: { form_number: '21-0779', confirmation_code: }
+ post '/simple_forms_api/v1/submit_scanned_form', params: params
expect(response).to have_http_status(:ok)
end
@@ -48,7 +62,27 @@
it 'stamps the pdf' do
expect(pdf_stamper).to receive(:stamp_pdf)
- post '/simple_forms_api/v1/submit_scanned_form', params: { form_number: '21-0779', confirmation_code: }
+ post '/simple_forms_api/v1/submit_scanned_form', params: params
+
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'checks if the prefill data has been changed' do
+ prefill_data = double
+ prefill_data_service = double
+ in_progress_form = double(form_data: prefill_data)
+
+ allow(SimpleFormsApi::PrefillDataService).to receive(:new).with(
+ prefill_data:,
+ form_data: hash_including(:email),
+ form_id: form_number
+ ).and_return(prefill_data_service)
+ allow(InProgressForm).to receive(:form_for_user).with('FORM-UPLOAD-FLOW',
+ anything).and_return(in_progress_form)
+
+ expect(prefill_data_service).to receive(:check_for_changes)
+
+ post '/simple_forms_api/v1/submit_scanned_form', params: params
expect(response).to have_http_status(:ok)
end
diff --git a/modules/simple_forms_api/spec/requests/simple_forms_api/v1/simple_forms_spec.rb b/modules/simple_forms_api/spec/requests/simple_forms_api/v1/simple_forms_spec.rb
index 774d8f4c6d1..a7aae4ed345 100644
--- a/modules/simple_forms_api/spec/requests/simple_forms_api/v1/simple_forms_spec.rb
+++ b/modules/simple_forms_api/spec/requests/simple_forms_api/v1/simple_forms_spec.rb
@@ -117,13 +117,13 @@
describe 'unauthenticated forms' do
unauthenticated_forms.each do |form|
- include_examples 'form submission', form, false
+ it_behaves_like 'form submission', form, false
end
end
describe 'authenticated forms' do
authenticated_forms.each do |form|
- include_examples 'form submission', form, true
+ it_behaves_like 'form submission', form, true
end
end
@@ -1016,9 +1016,8 @@
end
context 'rejected' do
- let(:reference_number) { 'some-reference-number' }
let(:body_status) { 'REJECTED' }
- let(:body) { { 'reference_number' => reference_number, 'status' => body_status } }
+ let(:body) { { 'status' => body_status } }
let(:status) { 200 }
let(:lgy_response) { double(body:, status:) }
@@ -1039,17 +1038,15 @@
'form26_4555_rejected_email_template_id',
{
'first_name' => 'Veteran',
- 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'),
- 'confirmation_number' => reference_number
+ 'date_submitted' => Time.zone.today.strftime('%B %d, %Y')
}
)
end
end
context 'duplicate' do
- let(:reference_number) { 'some-reference-number' }
let(:body_status) { 'DUPLICATE' }
- let(:body) { { 'reference_number' => reference_number, 'status' => body_status } }
+ let(:body) { { 'status' => body_status } }
let(:status) { 200 }
let(:lgy_response) { double(body:, status:) }
@@ -1070,8 +1067,7 @@
'form26_4555_duplicate_email_template_id',
{
'first_name' => 'Veteran',
- 'date_submitted' => Time.zone.today.strftime('%B %d, %Y'),
- 'confirmation_number' => reference_number
+ 'date_submitted' => Time.zone.today.strftime('%B %d, %Y')
}
)
end
diff --git a/modules/simple_forms_api/spec/services/notification_email_spec.rb b/modules/simple_forms_api/spec/services/notification_email_spec.rb
index b4ac437443e..40e0f172ad6 100644
--- a/modules/simple_forms_api/spec/services/notification_email_spec.rb
+++ b/modules/simple_forms_api/spec/services/notification_email_spec.rb
@@ -28,6 +28,28 @@
end
end
+ context '26-4555' do
+ let(:config) do
+ { form_data: {}, form_number: 'vba_26_4555', date_submitted: Time.zone.today.strftime('%B %d, %Y') }
+ end
+
+ context 'notification_type is duplicate' do
+ let(:notification_type) { :duplicate }
+
+ it 'does not require the confirmation_number' do
+ expect { described_class.new(config, notification_type:) }.not_to raise_error(ArgumentError)
+ end
+ end
+
+ context 'notification_type is rejeceted' do
+ let(:notification_type) { :rejected }
+
+ it 'does not require the confirmation_number' do
+ expect { described_class.new(config, notification_type:) }.not_to raise_error(ArgumentError)
+ end
+ end
+ end
+
context 'missing form_data' do
let(:config) do
{ form_number: 'vba_21_10210', confirmation_number: 'confirmation_number',
diff --git a/modules/simple_forms_api/spec/services/prefill_data_service_spec.rb b/modules/simple_forms_api/spec/services/prefill_data_service_spec.rb
new file mode 100644
index 00000000000..18deca25dbb
--- /dev/null
+++ b/modules/simple_forms_api/spec/services/prefill_data_service_spec.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require SimpleFormsApi::Engine.root.join('spec', 'spec_helper.rb')
+
+describe SimpleFormsApi::PrefillDataService do
+ describe '#check_for_changes' do
+ let(:form_id) { '21-0779' }
+ let(:rails_logger) { double }
+ let(:prefill_data) do
+ {
+ 'full_name' => {
+ 'first' => 'fake-first-name',
+ 'last' => 'fake-last-name'
+ },
+ 'address' => {
+ 'postal_code' => '12345'
+ },
+ 'veteran' => {
+ 'ssn' => 'fake-ssn'
+ },
+ 'email' => 'fake-email'
+ }
+ end
+ let(:form_data) do
+ {
+ 'full_name' => {
+ 'first' => 'fake-first-name',
+ 'last' => 'fake-last-name'
+ },
+ 'postal_code' => '12345',
+ 'id_number' => {
+ 'ssn' => 'fake-ssn'
+ },
+ 'email' => 'fake-email'
+ }
+ end
+ let(:prefill_data_service) do
+ SimpleFormsApi::PrefillDataService.new(
+ prefill_data: prefill_data.to_json, form_data: modified_form_data,
+ form_id:
+ )
+ end
+
+ before { allow(Rails).to receive(:logger).and_return(rails_logger) }
+
+ context 'first_name does not match' do
+ let(:modified_form_data) do
+ form_data.merge({ 'full_name' => {
+ 'first' => 'new-first-name',
+ 'last' => 'fake-last-name'
+ } })
+ end
+
+ it 'logs the first_name change' do
+ expect(rails_logger).to receive(:info).with('Simple forms api - Form Upload Flow changed data',
+ { field: :first_name, form_id: })
+
+ prefill_data_service.check_for_changes
+ end
+ end
+
+ context 'last_name does not match' do
+ let(:modified_form_data) do
+ form_data.merge({ 'full_name' => {
+ 'first' => 'fake-first-name',
+ 'last' => 'new-last-name'
+ } })
+ end
+
+ it 'logs the last_name change' do
+ expect(rails_logger).to receive(:info).with('Simple forms api - Form Upload Flow changed data',
+ { field: :last_name, form_id: })
+
+ prefill_data_service.check_for_changes
+ end
+ end
+
+ context 'postal_code does not match' do
+ let(:modified_form_data) do
+ form_data.merge({ 'postal_code' => '67890' })
+ end
+
+ it 'logs the postal_code change' do
+ expect(rails_logger).to receive(:info).with('Simple forms api - Form Upload Flow changed data',
+ { field: :postal_code, form_id: })
+
+ prefill_data_service.check_for_changes
+ end
+ end
+
+ context 'ssn does not match' do
+ let(:modified_form_data) do
+ form_data.merge({ 'id_number' => { 'ssn' => 'new-ssn' } })
+ end
+
+ it 'logs the ssn change' do
+ expect(rails_logger).to receive(:info).with('Simple forms api - Form Upload Flow changed data',
+ { field: :ssn, form_id: })
+
+ prefill_data_service.check_for_changes
+ end
+ end
+
+ context 'email does not match' do
+ let(:modified_form_data) do
+ form_data.merge({ 'email' => 'new-email' })
+ end
+
+ it 'logs the email change' do
+ expect(rails_logger).to receive(:info).with('Simple forms api - Form Upload Flow changed data',
+ { field: :email, form_id: })
+
+ prefill_data_service.check_for_changes
+ end
+ end
+ end
+end
diff --git a/modules/simple_forms_api/spec/support/shared_examples_for_base_form.rb b/modules/simple_forms_api/spec/support/shared_examples_for_base_form.rb
new file mode 100644
index 00000000000..a85f3c5680e
--- /dev/null
+++ b/modules/simple_forms_api/spec/support/shared_examples_for_base_form.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'zip_code_is_us_based' do |address_keys|
+ subject(:zip_code_is_us_based) { described_class.new(data).zip_code_is_us_based }
+
+ address_keys.each do |address_key|
+ context 'address is present and in US' do
+ let(:data) { { address_key => { 'country' => 'USA' } } }
+
+ it 'returns true' do
+ expect(zip_code_is_us_based).to eq(true)
+ end
+ end
+
+ context 'address is present and not in US' do
+ let(:data) { { address_key => { 'country' => 'Canada' } } }
+
+ it 'returns false' do
+ expect(zip_code_is_us_based).to eq(false)
+ end
+ end
+ end
+
+ context 'no valid address is given' do
+ let(:data) { {} }
+
+ it 'returns false' do
+ expect(zip_code_is_us_based).to eq(false)
+ end
+ end
+end
diff --git a/modules/travel_pay/app/controllers/travel_pay/application_controller.rb b/modules/travel_pay/app/controllers/travel_pay/application_controller.rb
index 7b64f7ebe98..85752a0b1ab 100644
--- a/modules/travel_pay/app/controllers/travel_pay/application_controller.rb
+++ b/modules/travel_pay/app/controllers/travel_pay/application_controller.rb
@@ -3,11 +3,8 @@
module TravelPay
class ApplicationController < ::ApplicationController
include ActionController::Cookies
- include ActionController::RequestForgeryProtection
service_tag 'travel-pay'
- protect_from_forgery with: :exception
-
before_action :authenticate
after_action :scrub_logs
diff --git a/modules/travel_pay/app/controllers/travel_pay/v0/claims_controller.rb b/modules/travel_pay/app/controllers/travel_pay/v0/claims_controller.rb
index 1de4bbe7448..8596e9a754e 100644
--- a/modules/travel_pay/app/controllers/travel_pay/v0/claims_controller.rb
+++ b/modules/travel_pay/app/controllers/travel_pay/v0/claims_controller.rb
@@ -34,12 +34,48 @@ def show
render json: claim, status: :ok
end
+ def create
+ unless Flipper.enabled?(:travel_pay_submit_mileage_expense, @current_user)
+ message = 'Travel Pay mileage expense submission unavailable per feature toggle'
+ raise Common::Exceptions::ServiceUnavailable, message:
+ end
+
+ begin
+ appt = appts_service.get_appointment_by_date_time({ 'appt_datetime' => params['appointmentDatetime'] })
+
+ claim = claims_service.create_new_claim({ 'btsss_appt_id' => appt[:data]['id'] })
+
+ claim_id = claim['claimId']
+
+ expense_service.add_expense({ 'claim_id' => claim_id, 'appt_date' => params['appointmentDatetime'] })
+
+ submitted_claim = claims_service.submit_claim(claim_id)
+ rescue ArgumentError => e
+ raise Common::Exceptions::BadRequest, detail: e.message
+ rescue Faraday::ClientError, Faraday::ServerError => e
+ raise Common::Exceptions::InternalServerError, exception: e
+ end
+
+ render json: submitted_claim, status: :created
+ end
+
private
+ def auth_manager
+ @auth_manager ||= TravelPay::AuthManager.new(Settings.travel_pay.client_number, @current_user)
+ end
+
def claims_service
- auth_manager = TravelPay::AuthManager.new(Settings.travel_pay.client_number, @current_user)
@claims_service ||= TravelPay::ClaimsService.new(auth_manager)
end
+
+ def appts_service
+ @appts_service ||= TravelPay::AppointmentsService.new(auth_manager)
+ end
+
+ def expense_service
+ @expense_service ||= TravelPay::ExpensesService.new(auth_manager)
+ end
end
end
end
diff --git a/modules/travel_pay/app/services/travel_pay/appointments_client.rb b/modules/travel_pay/app/services/travel_pay/appointments_client.rb
index d2e32a9d039..1b68ce55b75 100644
--- a/modules/travel_pay/app/services/travel_pay/appointments_client.rb
+++ b/modules/travel_pay/app/services/travel_pay/appointments_client.rb
@@ -24,9 +24,9 @@ def get_all_appointments(veis_token, btsss_token, params = {})
Rails.logger.debug(message: 'Correlation ID', correlation_id:)
query_path = if params.empty?
- 'api/v1.1/appointments'
+ 'api/v1.2/appointments'
else
- "api/v1.1/appointments?#{params.to_query}"
+ "api/v1.2/appointments?#{params.to_query}"
end
connection(server_url: btsss_url).get(query_path) do |req|
diff --git a/modules/travel_pay/app/services/travel_pay/appointments_service.rb b/modules/travel_pay/app/services/travel_pay/appointments_service.rb
index 6983e1db351..afdf7113cf2 100644
--- a/modules/travel_pay/app/services/travel_pay/appointments_service.rb
+++ b/modules/travel_pay/app/services/travel_pay/appointments_service.rb
@@ -54,7 +54,7 @@ def find_by_date_time(date_string, appointments)
end
rescue DateTime::Error => e
Rails.logger.error(message: "#{e} Invalid appointment time provided (given: #{date_string}).")
- raise ArgumentError, message: "#{e} Invalid appointment time provided (given: #{date_string})."
+ raise ArgumentError, "#{e} Invalid appointment time provided (given: #{date_string})."
end
def client
diff --git a/modules/travel_pay/app/services/travel_pay/claims_client.rb b/modules/travel_pay/app/services/travel_pay/claims_client.rb
index 653b27e160d..0d472757acc 100644
--- a/modules/travel_pay/app/services/travel_pay/claims_client.rb
+++ b/modules/travel_pay/app/services/travel_pay/claims_client.rb
@@ -16,7 +16,7 @@ def get_claims(veis_token, btsss_token)
correlation_id = SecureRandom.uuid
Rails.logger.debug(message: 'Correlation ID', correlation_id:)
- connection(server_url: btsss_url).get('api/v1/claims') do |req|
+ connection(server_url: btsss_url).get('api/v1.2/claims') do |req|
req.headers['Authorization'] = "Bearer #{veis_token}"
req.headers['BTSSS-Access-Token'] = btsss_token
req.headers['X-Correlation-ID'] = correlation_id
@@ -47,7 +47,7 @@ def get_claims_by_date(veis_token, btsss_token, params = {})
connection(server_url: btsss_url)
# URL subject to change once v1.2 is available (proposed endpoint: '/search')
- .get("api/v1.1/claims/search-by-appointment-date?#{url_params.to_query}") do |req|
+ .get("api/v1.2/claims/search-by-appointment-date?#{url_params.to_query}") do |req|
req.headers['Authorization'] = "Bearer #{veis_token}"
req.headers['BTSSS-Access-Token'] = btsss_token
req.headers['X-Correlation-ID'] = correlation_id
@@ -72,7 +72,7 @@ def create_claim(veis_token, btsss_token, params = {})
correlation_id = SecureRandom.uuid
Rails.logger.debug(message: 'Correlation ID', correlation_id:)
- connection(server_url: btsss_url).post('api/v1.1/claims') do |req|
+ connection(server_url: btsss_url).post('api/v1.2/claims') do |req|
req.headers['Authorization'] = "Bearer #{veis_token}"
req.headers['BTSSS-Access-Token'] = btsss_token
req.headers['X-Correlation-ID'] = correlation_id
@@ -84,5 +84,28 @@ def create_claim(veis_token, btsss_token, params = {})
}.to_json
end
end
+
+ ##
+ # HTTP POST call to the BTSSS 'claims/:id/submit' endpoint
+ # API responds with confirmation of claim submission
+ #
+ # @params {
+ # "claimId": "string",
+ # }
+ #
+ # @return Faraday::Response claim submission payload
+ #
+ def submit_claim(veis_token, btsss_token, claim_id)
+ btsss_url = Settings.travel_pay.base_url
+ correlation_id = SecureRandom.uuid
+ Rails.logger.debug(message: 'Correlation ID', correlation_id:)
+
+ connection(server_url: btsss_url).patch("api/v1.2/claims/#{claim_id}/submit") do |req|
+ req.headers['Authorization'] = "Bearer #{veis_token}"
+ req.headers['BTSSS-Access-Token'] = btsss_token
+ req.headers['X-Correlation-ID'] = correlation_id
+ req.headers.merge!(claim_headers)
+ end
+ end
end
end
diff --git a/modules/travel_pay/app/services/travel_pay/claims_service.rb b/modules/travel_pay/app/services/travel_pay/claims_service.rb
index d89ab84f7a0..a2e38eddf75 100644
--- a/modules/travel_pay/app/services/travel_pay/claims_service.rb
+++ b/modules/travel_pay/app/services/travel_pay/claims_service.rb
@@ -86,7 +86,26 @@ def create_new_claim(params = {})
@auth_manager.authorize => { veis_token:, btsss_token: }
new_claim_response = client.create_claim(veis_token, btsss_token, params)
- new_claim_response.body
+ new_claim_response.body['data']
+ end
+
+ def submit_claim(claim_id)
+ unless claim_id
+ raise ArgumentError,
+ message: 'You must provide a BTSSS claim ID to submit a claim.'
+ end
+
+ # ensure claim ID is the right format, allowing any version
+ uuid_all_version_format = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[89ABCD][0-9A-F]{3}-[0-9A-F]{12}$/i
+ unless uuid_all_version_format.match?(claim_id)
+ raise ArgumentError,
+ message: 'Expected BTSSS claim id to be a valid UUID'
+ end
+
+ @auth_manager.authorize => { veis_token:, btsss_token: }
+ submitted_claim_response = client.submit_claim(veis_token, btsss_token, claim_id)
+
+ submitted_claim_response.body['data']
end
private
diff --git a/modules/travel_pay/app/services/travel_pay/expenses_client.rb b/modules/travel_pay/app/services/travel_pay/expenses_client.rb
index 98683cda4f6..fd6b7f5be19 100644
--- a/modules/travel_pay/app/services/travel_pay/expenses_client.rb
+++ b/modules/travel_pay/app/services/travel_pay/expenses_client.rb
@@ -30,7 +30,7 @@ def add_mileage_expense(veis_token, btsss_token, params = {})
correlation_id = SecureRandom.uuid
Rails.logger.debug(message: 'Correlation ID', correlation_id:)
- connection(server_url: btsss_url).post('api/v1.1/expenses/mileage') do |req|
+ connection(server_url: btsss_url).post('api/v1.2/expenses/mileage') do |req|
req.headers['Authorization'] = "Bearer #{veis_token}"
req.headers['BTSSS-Access-Token'] = btsss_token
req.headers['X-Correlation-ID'] = correlation_id
diff --git a/modules/travel_pay/app/services/travel_pay/token_client.rb b/modules/travel_pay/app/services/travel_pay/token_client.rb
index 789e0d00113..c1916daccb0 100644
--- a/modules/travel_pay/app/services/travel_pay/token_client.rb
+++ b/modules/travel_pay/app/services/travel_pay/token_client.rb
@@ -38,7 +38,7 @@ def request_btsss_token(veis_token, user)
correlation_id = SecureRandom.uuid
Rails.logger.debug(message: 'Correlation ID', correlation_id:)
- response = connection(server_url: btsss_url).post('api/v1/Auth/access-token') do |req|
+ response = connection(server_url: btsss_url).post('api/v1.2/Auth/access-token') do |req|
req.headers['Authorization'] = "Bearer #{veis_token}"
req.headers['BTSSS-API-Client-Number'] = @client_number.to_s
req.headers['X-Correlation-ID'] = correlation_id
diff --git a/modules/travel_pay/spec/requests/travel_pay/claims_spec.rb b/modules/travel_pay/spec/requests/travel_pay/claims_spec.rb
index 70a417ec770..15a99b91d7d 100644
--- a/modules/travel_pay/spec/requests/travel_pay/claims_spec.rb
+++ b/modules/travel_pay/spec/requests/travel_pay/claims_spec.rb
@@ -101,4 +101,68 @@
expect(response).to have_http_status(:service_unavailable)
end
end
+
+ describe '#create' do
+ before do
+ Flipper.enable(:travel_pay_submit_mileage_expense)
+ end
+
+ it 'returns a ServiceUnavailable response if feature flag turned off' do
+ Flipper.disable(:travel_pay_submit_mileage_expense)
+
+ headers = { 'Authorization' => 'Bearer vagov_token' }
+ params = {}
+
+ post '/travel_pay/v0/claims', headers: headers, params: params
+
+ expect(response).to have_http_status(:service_unavailable)
+ end
+
+ it 'returns a successfully submitted claim response' do
+ allow_any_instance_of(TravelPay::AuthManager).to receive(:authorize)
+ .and_return({ veis_token: 'vt', btsss_token: 'bt' })
+
+ VCR.use_cassette('travel_pay/submit/success', match_requests_on: %i[method path]) do
+ headers = { 'Authorization' => 'Bearer vagov_token' }
+ params = { 'appointmentDatetime' => '2024-01-01T16:45:34.465Z' }
+
+ post '/travel_pay/v0/claims', headers: headers, params: params
+
+ expect(response).to have_http_status(:created)
+ end
+ end
+
+ it 'returns a BadRequest response if an invalid appointment date time is given' do
+ allow_any_instance_of(TravelPay::AuthManager).to receive(:authorize)
+ .and_return({ veis_token: 'vt', btsss_token: 'bt' })
+
+ VCR.use_cassette('travel_pay/submit/success', match_requests_on: %i[method path]) do
+ headers = { 'Authorization' => 'Bearer vagov_token' }
+ params = { 'appointmentDatetime' => 'My birthday, 4 years ago' }
+
+ post '/travel_pay/v0/claims', headers: headers, params: params
+
+ error_detail = JSON.parse(response.body)['errors'][0]['detail']
+ expect(response).to have_http_status(:bad_request)
+ expect(error_detail).to match(/date/)
+ end
+ end
+
+ it 'returns a server error response if a request to the Travel Pay API fails' do
+ allow_any_instance_of(TravelPay::AuthManager).to receive(:authorize)
+ .and_return({ veis_token: 'vt', btsss_token: 'bt' })
+ allow_any_instance_of(TravelPay::ClaimsService).to receive(:submit_claim)
+ .and_raise(Common::Exceptions::InternalServerError.new(Faraday::ServerError.new))
+
+ # The cassette doesn't matter here as I'm mocking the submit_claim method
+ VCR.use_cassette('travel_pay/submit/success', match_requests_on: %i[method path]) do
+ headers = { 'Authorization' => 'Bearer vagov_token' }
+ params = { 'appointmentDatetime' => '2024-01-01T16:45:34.465Z' }
+
+ post '/travel_pay/v0/claims', headers: headers, params: params
+
+ expect(response).to have_http_status(:internal_server_error)
+ end
+ end
+ end
end
diff --git a/modules/travel_pay/spec/services/appointments_client_spec.rb b/modules/travel_pay/spec/services/appointments_client_spec.rb
index 0b43acee03b..a4076625b48 100644
--- a/modules/travel_pay/spec/services/appointments_client_spec.rb
+++ b/modules/travel_pay/spec/services/appointments_client_spec.rb
@@ -89,7 +89,7 @@
context '/appointments' do
it 'returns a response only with appointments with no claims' do
- @stubs.get('/api/v1.1/appointments?excludeWithClaims=true') do
+ @stubs.get('/api/v1.2/appointments?excludeWithClaims=true') do
[
200,
{},
@@ -109,7 +109,7 @@
end
it 'returns a response with all appointments' do
- @stubs.get('/api/v1.1/appointments') do
+ @stubs.get('/api/v1.2/appointments') do
[
200,
{},
diff --git a/modules/travel_pay/spec/services/claims_client_spec.rb b/modules/travel_pay/spec/services/claims_client_spec.rb
index 1e2832b281d..883cce2242c 100644
--- a/modules/travel_pay/spec/services/claims_client_spec.rb
+++ b/modules/travel_pay/spec/services/claims_client_spec.rb
@@ -8,13 +8,11 @@
before do
@stubs = Faraday::Adapter::Test::Stubs.new
- conn = Faraday.new do |c|
+ @conn = Faraday.new do |c|
c.adapter(:test, @stubs)
c.response :json
c.request :json
end
-
- allow_any_instance_of(TravelPay::ClaimsClient).to receive(:connection).and_return(conn)
end
context 'prod settings' do
@@ -38,7 +36,8 @@
context '/claims' do
# GET
it 'returns response from claims endpoint' do
- @stubs.get('/api/v1/claims') do
+ allow_any_instance_of(TravelPay::ClaimsClient).to receive(:connection).and_return(@conn)
+ @stubs.get('/api/v1.2/claims') do
[
200,
{},
@@ -86,7 +85,8 @@
end
it 'returns response from claims/search endpoint' do
- @stubs.get('api/v1.1/claims/search-by-appointment-date') do
+ allow_any_instance_of(TravelPay::ClaimsClient).to receive(:connection).and_return(@conn)
+ @stubs.get('api/v1.2/claims/search-by-appointment-date') do
[
200,
{},
@@ -126,12 +126,13 @@
expect(actual_ids).to eq(expected)
end
- # POST create_claim
+ # PATCH submit_claim
it 'returns a claim ID from the claims endpoint' do
+ allow_any_instance_of(TravelPay::ClaimsClient).to receive(:connection).and_return(@conn)
claim_id = '3fa85f64-5717-4562-b3fc-2c963f66afa6'
body = { 'appointmentId' => 'fake_btsss_appt_id', 'claimName' => 'SMOC claim',
'claimantType' => 'Veteran' }.to_json
- @stubs.post('api/v1.1/claims') do
+ @stubs.post('api/v1.2/claims') do
[
200,
{},
@@ -150,5 +151,15 @@
expect(actual_claim_id).to eq(claim_id)
end
+
+ # PATCH submit_claim
+ it 'returns a claim ID from the claims endpoint after submitting a claim' do
+ claim_id = '3fa85f64-5717-4562-b3fc-2c963f66afa6'
+
+ expect_any_instance_of(Faraday::Connection).to receive(:patch).with("api/v1.2/claims/#{claim_id}/submit")
+
+ client = TravelPay::ClaimsClient.new
+ client.submit_claim('veis_token', 'btsss_token', claim_id)
+ end
end
end
diff --git a/modules/travel_pay/spec/services/claims_service_spec.rb b/modules/travel_pay/spec/services/claims_service_spec.rb
index 18110cc0890..c9ad9087f49 100644
--- a/modules/travel_pay/spec/services/claims_service_spec.rb
+++ b/modules/travel_pay/spec/services/claims_service_spec.rb
@@ -347,8 +347,7 @@
'btsss_appt_id' => btsss_appt_id,
'claim_name' => 'SMOC claim'
})
-
- expect(actual_claim_response['data']).to equal(new_claim_data['data'])
+ expect(actual_claim_response).to equal(new_claim_data['data'])
end
it 'throws an ArgumentException if btsss_appt_id is invalid format' do
@@ -361,4 +360,41 @@
.to raise_error(ArgumentError, /must provide/i)
end
end
+
+ context 'submit claim' do
+ let(:user) { build(:user) }
+ let(:response) do
+ Faraday::Response.new(
+ body: { 'data' => { 'claimId' => '3fa85f64-5717-4562-b3fc-2c963f66afa6',
+ 'status' => 'InProcess' } }
+ )
+ end
+
+ let(:tokens) { { veis_token: 'veis_token', btsss_token: 'btsss_token' } }
+
+ before do
+ auth_manager = object_double(TravelPay::AuthManager.new(123, user), authorize: tokens)
+ @service = TravelPay::ClaimsService.new(auth_manager)
+ end
+
+ it 'returns submitted claim information' do
+ expect_any_instance_of(TravelPay::ClaimsClient)
+ .to receive(:submit_claim).once
+ .and_return(response)
+
+ @service.submit_claim('3fa85f64-5717-4562-b3fc-2c963f66afa6')
+ end
+
+ it 'raises an error if claim_id is missing' do
+ expect { @service.submit_claim }.to raise_error(ArgumentError)
+ end
+
+ it 'raises an error if invalid claim_id provided' do
+ # present, wrong format
+ expect { @service.submit_claim('claim_numero_uno') }.to raise_error(ArgumentError)
+
+ # empty
+ expect { @service.submit_claim('') }.to raise_error(ArgumentError)
+ end
+ end
end
diff --git a/modules/travel_pay/spec/services/expenses_client_spec.rb b/modules/travel_pay/spec/services/expenses_client_spec.rb
index b727090c767..0f1469f5183 100644
--- a/modules/travel_pay/spec/services/expenses_client_spec.rb
+++ b/modules/travel_pay/spec/services/expenses_client_spec.rb
@@ -21,7 +21,7 @@
# POST add_expense
it 'returns an expenseId from the /expenses/mileage endpoint' do
expense_id = '3fa85f64-5717-4562-b3fc-2c963f66afa6'
- @stubs.post('/api/v1.1/expenses/mileage') do
+ @stubs.post('/api/v1.2/expenses/mileage') do
[
200,
{},
diff --git a/modules/travel_pay/spec/services/token_client_spec.rb b/modules/travel_pay/spec/services/token_client_spec.rb
index 9b582fa16e2..bc1f30ae90c 100644
--- a/modules/travel_pay/spec/services/token_client_spec.rb
+++ b/modules/travel_pay/spec/services/token_client_spec.rb
@@ -43,7 +43,7 @@
end
it 'returns btsss token from proper endpoint' do
- @stubs.post('api/v1/Auth/access-token') do
+ @stubs.post('api/v1.2/Auth/access-token') do
[
200,
{ 'Content-Type': 'application/json' },
diff --git a/modules/va_forms/.bruno_va_forms/Get form.bru b/modules/va_forms/.bruno_va_forms/Get form.bru
deleted file mode 100644
index 8b27326bfc3..00000000000
--- a/modules/va_forms/.bruno_va_forms/Get form.bru
+++ /dev/null
@@ -1,23 +0,0 @@
-meta {
- name: Get form
- type: http
- seq: 1
-}
-
-get {
- url: {{base_uri}}/services/va_forms/v0/forms/{{form_name}}
- body: none
- auth: none
-}
-
-headers {
- apikey: {{api_key}}
-}
-
-vars:pre-request {
- ~form_name: 29-4364
-}
-
-assert {
- res.status: eq 200
-}
diff --git a/modules/va_forms/.bruno_va_forms/Search forms.bru b/modules/va_forms/.bruno_va_forms/Search forms.bru
deleted file mode 100644
index 29a01389f19..00000000000
--- a/modules/va_forms/.bruno_va_forms/Search forms.bru
+++ /dev/null
@@ -1,23 +0,0 @@
-meta {
- name: Search forms
- type: http
- seq: 2
-}
-
-get {
- url: {{base_uri}}/services/va_forms/v0/forms?query=10-0381
- body: none
- auth: none
-}
-
-query {
- query: 10-0381
-}
-
-headers {
- apikey: {{api_key}}
-}
-
-assert {
- res.status: eq 200
-}
diff --git a/modules/va_forms/.bruno_va_forms/bruno.json b/modules/va_forms/.bruno_va_forms/bruno.json
deleted file mode 100755
index 17210449bf8..00000000000
--- a/modules/va_forms/.bruno_va_forms/bruno.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "version": "1",
- "name": "VA Forms API",
- "type": "collection"
-}
\ No newline at end of file
diff --git a/modules/va_forms/.bruno_va_forms/environments/Development.bru b/modules/va_forms/.bruno_va_forms/environments/Development.bru
deleted file mode 100644
index f137cd6032d..00000000000
--- a/modules/va_forms/.bruno_va_forms/environments/Development.bru
+++ /dev/null
@@ -1,6 +0,0 @@
-vars {
- base_uri: https://dev-api.va.gov
-}
-vars:secret [
- api_key
-]
diff --git a/modules/va_forms/.bruno_va_forms/environments/Local.bru b/modules/va_forms/.bruno_va_forms/environments/Local.bru
deleted file mode 100755
index 3ff43baf9d4..00000000000
--- a/modules/va_forms/.bruno_va_forms/environments/Local.bru
+++ /dev/null
@@ -1,6 +0,0 @@
-vars {
- base_uri: http://localhost:3000
-}
-vars:secret [
- api_key
-]
diff --git a/modules/va_forms/.bruno_va_forms/environments/Production.bru b/modules/va_forms/.bruno_va_forms/environments/Production.bru
deleted file mode 100644
index 2d6d61f4f41..00000000000
--- a/modules/va_forms/.bruno_va_forms/environments/Production.bru
+++ /dev/null
@@ -1,6 +0,0 @@
-vars {
- base_uri: https://api.va.gov
-}
-vars:secret [
- api_key
-]
diff --git a/modules/va_forms/.bruno_va_forms/environments/Sandbox.bru b/modules/va_forms/.bruno_va_forms/environments/Sandbox.bru
deleted file mode 100644
index 1950227043f..00000000000
--- a/modules/va_forms/.bruno_va_forms/environments/Sandbox.bru
+++ /dev/null
@@ -1,6 +0,0 @@
-vars {
- base_uri: https://sandbox-api.va.gov
-}
-vars:secret [
- api_key
-]
diff --git a/modules/va_forms/.bruno_va_forms/environments/Staging.bru b/modules/va_forms/.bruno_va_forms/environments/Staging.bru
deleted file mode 100755
index dddddee3ebe..00000000000
--- a/modules/va_forms/.bruno_va_forms/environments/Staging.bru
+++ /dev/null
@@ -1,6 +0,0 @@
-vars {
- base_uri: https://staging-api.va.gov
-}
-vars:secret [
- api_key
-]
diff --git a/modules/va_forms/.rubocop.yml b/modules/va_forms/.rubocop.yml
deleted file mode 100644
index a2d5d27b528..00000000000
--- a/modules/va_forms/.rubocop.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-inherit_from:
- - ../../.rubocop.yml
-
-AllCops:
- Exclude:
- - 'bin/*'
- - bin/rails
- - db/migrate/*.rb
- - 'spec/dummy/**/*'
- - spec/dummy/db/schema.rb
-
-Metrics/BlockLength:
- Exclude:
- - config/*.rb
- - spec/**/*.rb
- - app/swagger/**/*.rb
-
-Metrics/ClassLength:
- Exclude:
- - app/swagger/**/*.rb
-
-Layout/LineLength:
- Exclude:
- - app/swagger/**/*.rb
diff --git a/modules/va_forms/Gemfile b/modules/va_forms/Gemfile
deleted file mode 100644
index 5676bcec800..00000000000
--- a/modules/va_forms/Gemfile
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-source 'https://rubygems.org'
-
-# Declare your gem's dependencies in vba_documents.gemspec.
-# Bundler will treat runtime dependencies like base dependencies, and
-# development dependencies will be added by default to the :development group.
-gemspec
-
-# Declare any dependencies that are still in development here instead of in
-# your gemspec. These might include edge Rails or gems from your path or
-# Git. Remember to move these dependencies to your gemspec before releasing
-# your gem to rubygems.org.
-
-# To use a debugger
-# gem 'byebug', group: [:development, :test]
diff --git a/modules/va_forms/Rakefile b/modules/va_forms/Rakefile
deleted file mode 100644
index 45420c747f9..00000000000
--- a/modules/va_forms/Rakefile
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-begin
- require 'bundler/setup'
-rescue LoadError
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
-end
-
-APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
-load 'rails/tasks/engine.rake'
-
-load 'rails/tasks/statistics.rake'
-
-Bundler::GemHelper.install_tasks
-
-Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
-
-require 'rspec/core'
-require 'rspec/core/rake_task'
-
-desc 'Run all specs in spec directory (excluding plugin specs)'
-RSpec::Core::RakeTask.new(spec: 'app:db:test:prepare')
-
-task default: :spec
diff --git a/modules/va_forms/app/controllers/va_forms/application_controller.rb b/modules/va_forms/app/controllers/va_forms/application_controller.rb
deleted file mode 100644
index 8f75af8d122..00000000000
--- a/modules/va_forms/app/controllers/va_forms/application_controller.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- class ApplicationController < ::ApplicationController
- service_tag 'lighthouse-forms'
- skip_before_action :verify_authenticity_token
- skip_after_action :set_csrf_header
- before_action :require_gateway_origin
-
- def require_gateway_origin
- raise Common::Exceptions::Unauthorized if Rails.env.production? \
- && (request.headers['X-Consumer-ID'].blank? || request.headers['X-Consumer-Username'].blank?) \
- && Flipper.enabled?(:benefits_require_gateway_origin)
- end
- end
-end
diff --git a/modules/va_forms/app/controllers/va_forms/docs/v0/api_controller.rb b/modules/va_forms/app/controllers/va_forms/docs/v0/api_controller.rb
deleted file mode 100644
index 51f220e449d..00000000000
--- a/modules/va_forms/app/controllers/va_forms/docs/v0/api_controller.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- module Docs
- module V0
- class ApiController < ApplicationController
- skip_before_action(:authenticate)
- include Swagger::Blocks
-
- SWAGGERED_CLASSES = [
- VAForms::V0::ControllerSwagger,
- VAForms::Forms::FormSwagger,
- VAForms::V0::SecuritySchemeSwagger,
- VAForms::V0::SwaggerRoot
- ].freeze
-
- def index
- render json: Swagger::Blocks.build_root_json(SWAGGERED_CLASSES)
- end
- end
- end
- end
-end
diff --git a/modules/va_forms/app/controllers/va_forms/metadata_controller.rb b/modules/va_forms/app/controllers/va_forms/metadata_controller.rb
deleted file mode 100644
index 83b3983fba4..00000000000
--- a/modules/va_forms/app/controllers/va_forms/metadata_controller.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-require 'va_forms/health_checker'
-
-module VAForms
- class MetadataController < ::ApplicationController
- service_tag 'lighthouse-forms'
- skip_before_action :verify_authenticity_token
- skip_after_action :set_csrf_header
- skip_before_action(:authenticate)
- include HealthChecker::Constants
-
- def index
- render json: {
- meta: {
- versions: [
- {
- version: '0.0.1',
- internal_only: false,
- status: VERSION_STATUS[:current],
- path: '/services/va_forms/docs/v0/api',
- healthcheck: '/services/va_forms/v0/healthcheck'
- }
- ]
- }
- }
- end
-
- def healthcheck
- render json: {
- description: HEALTH_DESCRIPTION,
- status: 'UP',
- time: Time.zone.now.to_formatted_s(:iso8601)
- }
- end
-
- def upstream_healthcheck
- health_checker = VAForms::HealthChecker.new
- time = Time.zone.now.to_formatted_s(:iso8601)
-
- render json: {
- description: HEALTH_DESCRIPTION_UPSTREAM,
- status: health_checker.services_are_healthy? ? 'UP' : 'DOWN',
- time:,
- details: {
- name: 'All upstream services',
- upstreamServices: VAForms::HealthChecker::SERVICES.map do |service|
- upstream_service_details(service, health_checker, time)
- end
- }
- }, status: health_checker.services_are_healthy? ? 200 : 503
- end
-
- private
-
- def upstream_service_details(service_name, health_checker, time)
- healthy = health_checker.healthy_service?(service_name)
-
- {
- description: service_name.titleize,
- status: healthy ? 'UP' : 'DOWN',
- details: {
- name: service_name.titleize,
- statusCode: healthy ? 200 : 503,
- status: healthy ? 'OK' : 'Unavailable',
- time:
- }
- }
- end
- end
-end
diff --git a/modules/va_forms/app/controllers/va_forms/v0/forms_controller.rb b/modules/va_forms/app/controllers/va_forms/v0/forms_controller.rb
deleted file mode 100644
index 407020de950..00000000000
--- a/modules/va_forms/app/controllers/va_forms/v0/forms_controller.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-require 'va_forms/regex_helper'
-
-module VAForms
- module V0
- class FormsController < ApplicationController
- skip_before_action(:authenticate)
-
- def index
- if params[:query].present?
- # Checks to see if a form follows the SF/VA DD(p)-DDDD format
- params[:query].strip!
- valid_search_regex = /^\d{2}[pP]?-\d+(?:-)?[a-zA-Z]{0,2}(?:-.)?$/
- return search_by_form_number if params[:query].match(valid_search_regex).present?
-
- return search_by_text(VAForms::RegexHelper.new.scrub_query(params[:query]))
- end
- return_all
- end
-
- def search_by_form_number
- forms = Form.search_by_form_number(params[:query])
- render json: VAForms::FormListSerializer.new(forms)
- end
-
- def search_by_text(query)
- forms = Form.search(query)
- render json: VAForms::FormListSerializer.new(forms)
- end
-
- def return_all
- forms = Form.return_all
- render json: VAForms::FormListSerializer.new(forms)
- end
-
- def show
- forms = Form.find_by form_name: params[:id]
- if forms.present?
- render json: VAForms::FormDetailSerializer.new(forms)
- else
- render json: { errors: [{ detail: 'Form not found' }] }, status: :not_found
- end
- end
- end
- end
-end
diff --git a/modules/va_forms/app/models/va_forms/form.rb b/modules/va_forms/app/models/va_forms/form.rb
deleted file mode 100644
index 20de1cad97a..00000000000
--- a/modules/va_forms/app/models/va_forms/form.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- class Form < ApplicationRecord
- include PgSearch::Model
- pg_search_scope :search,
- against: { tags: 'A',
- title: 'B',
- form_name: 'C' },
- using: { tsearch: { normalization: 4, any_word: true, prefix: true, dictionary: 'english' },
- trigram: {
- word_similarity: true
- } },
- order_within_rank: 'va_forms_forms.ranking ASC, va_forms_forms.language ASC'
-
- validates :title, presence: true
- validates :form_name, presence: true
- validates :row_id, uniqueness: true
- validates :url, presence: true
- validates :valid_pdf, inclusion: { in: [true, false] }
-
- before_save :set_revision
- before_save :set_sha256_history
-
- FORM_BASE_URL = 'https://www.va.gov'
-
- def self.return_all
- Form.all.sort_by(&:updated_at)
- end
-
- def self.search_by_form_number(search_term)
- Form.where('upper(form_name) LIKE ?', "%#{search_term.upcase}%")
- .order(form_name: :asc, deleted_at: :desc, language: :asc, title: :asc)
- end
-
- def self.old_search(search_term: nil)
- query = Form.all
- if search_term.present?
- search_term.strip!
- terms = search_term.split.map { |term| "%#{term}%" }
- query = query.where('form_name ilike ANY ( array[?] ) OR title ilike ANY ( array[?] )', terms, terms)
- end
- query
- end
-
- def self.normalized_form_url(url)
- url = url.starts_with?('http') ? url.gsub('http:', 'https:') : expanded_va_url(url)
- Addressable::URI.parse(url).normalize.to_s
- end
-
- def self.expanded_va_url(url)
- raise ArgumentError, 'url must start with ./va or ./medical' unless url.starts_with?('./va', './medical')
-
- "#{FORM_BASE_URL}/vaforms/#{url.gsub('./', '')}" if url.starts_with?('./va') || url.starts_with?('./medical')
- end
-
- private
-
- def set_revision
- self.last_revision_on = first_issued_on if last_revision_on.blank?
- end
-
- def set_sha256_history
- if sha256.present? && sha256_changed?
- self.last_sha256_change = Time.zone.today
-
- current_history = change_history&.dig('versions')
- new_history = { sha256:, revision_on: last_sha256_change.strftime('%Y-%m-%d') }
-
- if current_history.present? && current_history.is_a?(Array)
- change_history['versions'] << new_history
- else
- self.change_history = { versions: [new_history] }
- end
- end
- end
- end
-end
diff --git a/modules/va_forms/app/serializers/va_forms/form_detail_serializer.rb b/modules/va_forms/app/serializers/va_forms/form_detail_serializer.rb
deleted file mode 100644
index df223ab858a..00000000000
--- a/modules/va_forms/app/serializers/va_forms/form_detail_serializer.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- class FormDetailSerializer
- include JSONAPI::Serializer
-
- set_type :va_form
- set_id :row_id
-
- attributes :form_name, :url, :title, :first_issued_on,
- :last_revision_on, :created_at, :pages, :sha256, :valid_pdf,
- :form_usage, :form_tool_intro, :form_tool_url, :form_details_url,
- :form_type, :language, :deleted_at, :related_forms,
- :benefit_categories, :va_form_administration
-
- attribute :versions do |object|
- object.change_history&.dig('versions') || []
- end
- end
-end
diff --git a/modules/va_forms/app/serializers/va_forms/form_list_serializer.rb b/modules/va_forms/app/serializers/va_forms/form_list_serializer.rb
deleted file mode 100644
index a98522efaec..00000000000
--- a/modules/va_forms/app/serializers/va_forms/form_list_serializer.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- class FormListSerializer
- include JSONAPI::Serializer
-
- set_type :va_form
- set_id :row_id
-
- attributes :form_name, :url, :title, :first_issued_on,
- :last_revision_on, :pages, :sha256, :last_sha256_change, :valid_pdf,
- :form_usage, :form_tool_intro, :form_tool_url, :form_details_url,
- :form_type, :language, :deleted_at, :related_forms, :benefit_categories,
- :va_form_administration
- end
-end
diff --git a/modules/va_forms/app/services/va_forms/slack/hash_notification.rb b/modules/va_forms/app/services/va_forms/slack/hash_notification.rb
deleted file mode 100644
index db167c75c2d..00000000000
--- a/modules/va_forms/app/services/va_forms/slack/hash_notification.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- module Slack
- class HashNotification
- def initialize(params)
- # Params are expected to be in the Sidekiq Job Format (https://github.com/mperham/sidekiq/wiki/Job-Format),
- @params = params
- end
-
- def message_text
- msg = "ENVIRONMENT: #{environment}".dup
-
- params.each do |k, v|
- msg << "\n#{k.to_s.upcase} : #{v}"
- end
-
- msg
- end
-
- private
-
- attr_accessor :params
-
- def environment
- env = Settings.vsp_environment
-
- env_emoji = Messenger::ENVIRONMENT_EMOJIS[env.to_sym]
-
- ":#{env_emoji}: #{env} :#{env_emoji}:"
- end
- end
- end
-end
diff --git a/modules/va_forms/app/services/va_forms/slack/messenger.rb b/modules/va_forms/app/services/va_forms/slack/messenger.rb
deleted file mode 100644
index 8f63477050a..00000000000
--- a/modules/va_forms/app/services/va_forms/slack/messenger.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require 'common/client/base'
-
-module VAForms
- module Slack
- class Messenger
- API_PATH = 'https://slack.com/api/chat.postMessage'
- ENVIRONMENT_EMOJIS = { production: 'rotating_light', sandbox: 'rocket', staging: 'construction',
- development: 'brain', localhost: 'test_tube' }.freeze
-
- def initialize(params)
- @params = params
- end
-
- def notify!
- return unless Settings.va_forms.slack.enabled
-
- Faraday.post(API_PATH, request_body, request_headers)
- end
-
- private
-
- attr_reader :params
-
- def notification
- VAForms::Slack::HashNotification.new(params)
- end
-
- def request_body
- {
- text: notification.message_text,
- channel: slack_channel_id
- }.to_json
- end
-
- def request_headers
- {
- 'Content-type' => 'application/json; charset=utf-8',
- 'Authorization' => "Bearer #{slack_api_token}"
- }
- end
-
- def slack_channel_id
- Settings.va_forms.slack.channel_id
- end
-
- def slack_api_token
- Settings.va_forms.slack.api_key
- end
- end
- end
-end
diff --git a/modules/va_forms/app/services/va_forms/update_form_tags_service.rb b/modules/va_forms/app/services/va_forms/update_form_tags_service.rb
deleted file mode 100644
index 095bf6f27da..00000000000
--- a/modules/va_forms/app/services/va_forms/update_form_tags_service.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- class UpdateFormTagsService
- def run
- Rails.logger.info('Running VAForms::UpdateFormTagsService - Adding form tags')
-
- form_tags['form tags list'].each do |tag_info|
- tags_to_add = tag_info['tags']
- tag_info['form_names'].each { |form_name| add_tags_to_form(form_name, tags_to_add) }
- end
- end
-
- def self.run
- new.run
- end
-
- private
-
- def form_tags
- YAML.load_file(update_form_tags_yaml_path)
- end
-
- def update_form_tags_yaml_path
- Rails.root.join('modules', 'va_forms', 'config', 'update_form_tags.yaml').to_s
- end
-
- def add_tags_to_form(form_name, tags_to_add)
- form = VAForms::Form.find_by(form_name:)
-
- return if form.blank?
-
- tags_to_add.each do |tag|
- next if form.tags.present? && form.tags.match(/\s#{tag}\s?/)
-
- form.tags = "#{form.tags} #{tag}"
- form.save
- end
- rescue
- Rails.logger.send(:error, "VAForms:UpdateFormTagsService failed to add tags to form_name:#{form_name},
- tags_to_add:#{tags_to_add}")
- end
- end
-end
diff --git a/modules/va_forms/app/sidekiq/va_forms/flipper_status_alert.rb b/modules/va_forms/app/sidekiq/va_forms/flipper_status_alert.rb
deleted file mode 100644
index 11a10679e4a..00000000000
--- a/modules/va_forms/app/sidekiq/va_forms/flipper_status_alert.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# frozen_string_literal: true
-
-require 'sidekiq'
-require 'flipper/utilities/bulk_feature_checker'
-
-module VAForms
- class FlipperStatusAlert
- include Sidekiq::Job
-
- WARNING_EMOJI = ':warning:'
- TRAFFIC_LIGHT_EMOJI = ':vertical_traffic_light:'
-
- sidekiq_options retry: 5, unique_for: 30.minutes
-
- def perform
- features_to_check = load_features_from_config
- if features_to_check.present?
- feature_statuses = Flipper::Utilities::BulkFeatureChecker.enabled_status(features_to_check)
- notify_slack(feature_statuses[:disabled]) unless feature_statuses[:disabled].empty?
- end
- end
-
- private
-
- def load_features_from_config
- file_path = VAForms::Engine.root.join('config', 'flipper', 'enabled_features.yml')
- feature_hash = read_config_file(file_path)
- return [] if feature_hash.nil?
-
- env_hash = feature_hash.fetch(Settings.vsp_environment.to_s, []) || []
-
- (env_hash + (feature_hash['common'] || [])).uniq.sort
- end
-
- def read_config_file(path)
- if File.exist?(path)
- YAML.load_file(path) || {}
- else
- VAForms::Slack::Messenger.new(
- {
- warning: "#{WARNING_EMOJI} #{self.class.name} features file does not exist.",
- file_path: path.to_s
- }
- ).notify!
- nil
- end
- end
-
- def notify_slack(disabled_features)
- slack_details = {
- class: self.class.name,
- warning: "#{WARNING_EMOJI} One or more features expected to be enabled were found to be disabled.",
- disabled_flags: "#{TRAFFIC_LIGHT_EMOJI} #{disabled_features.join(', ')} #{TRAFFIC_LIGHT_EMOJI}"
- }
- VAForms::Slack::Messenger.new(slack_details).notify!
- end
- end
-end
diff --git a/modules/va_forms/app/sidekiq/va_forms/form_builder.rb b/modules/va_forms/app/sidekiq/va_forms/form_builder.rb
deleted file mode 100644
index 49b2d8b9026..00000000000
--- a/modules/va_forms/app/sidekiq/va_forms/form_builder.rb
+++ /dev/null
@@ -1,211 +0,0 @@
-# frozen_string_literal: true
-
-require 'sidekiq'
-require 'va_forms/regex_helper'
-
-module VAForms
- class FormBuilder
- include Sidekiq::Job
-
- class FormFetchError < StandardError; end
-
- STATSD_KEY_PREFIX = 'api.va_forms.form_builder'
-
- NON_RETRYABLE_ERROR_CODES = [403, 404].freeze # No need to retry because unlikely to recover
-
- sidekiq_options retry: 7
-
- sidekiq_retries_exhausted do |msg, _ex|
- job_id = msg['jid']
- job_class = msg['class']
- error_class = msg['error_class']
- error_message = msg['error_message']
-
- form_data = msg['args'].first
- form_name = form_data['fieldVaFormNumber']
- row_id = form_data['fieldVaFormRowId']
-
- # Ensure that the form is marked "valid_pdf: false" if successive form fetches have failed
- if error_class.to_s == FormFetchError.to_s
- form = VAForms::Form.find_by(row_id:)
- url = VAForms::Form.normalized_form_url(form_data.dig('fieldVaFormUrl', 'uri'))
- form_previously_valid = form.valid_pdf
- form.update!(valid_pdf: false, sha256: nil, url:)
- if form_previously_valid
- VAForms::Slack::Messenger.new(
- {
- class: job_class.to_s,
- message: "URL for form #{form_name} no longer returns a valid PDF or web page.",
- form_url: url
- }
- ).notify!
- end
- end
-
- StatsD.increment("#{STATSD_KEY_PREFIX}.exhausted", tags: { form_name:, row_id: })
-
- Rails.logger.warn(
- "#{job_class} retries exhausted",
- { job_id:, error_class:, error_message:, form_name:, row_id:, form_data: }
- )
- rescue => e
- Rails.logger.error(
- "Failure in #{job_class}#sidekiq_retries_exhausted",
- {
- messaged_content: e.message,
- job_id:,
- form_name:,
- row_id:,
- pre_exhaustion_failure: {
- error_class:,
- error_message:
- }
- }
- )
- raise e
- end
-
- def perform(form_data)
- build_and_save_form(form_data)
- end
-
- private
-
- # Given +form_data+ as returned by the graphql query, returns a matching, updated and saved +VAForms::Form+ instance
- def build_and_save_form(form_data)
- form = find_or_initialize_form(form_data)
- attrs = gather_form_attributes(form_data)
- url = VAForms::Form.normalized_form_url(form_data.dig('fieldVaFormUrl', 'uri'))
- attrs[:url] = url if form.new_record?
- form.update!(attrs) # The job can fail later, so save current progress
-
- if form.deleted_at.present?
- form.update!(valid_pdf: false, sha256: nil)
- else
- attrs = check_form_validity(form, attrs, url)
- form.update!(attrs)
- end
- end
-
- # Given a +form+, +attrs+, and +url+, makes a request for the form; if response is successful, assigns attributes
- # and sends Slack notifications; if response is unsuccessful, raises an error
- def check_form_validity(form, attrs, url)
- response = fetch_form(url)
- if response.success? || NON_RETRYABLE_ERROR_CODES.include?(response.status)
- attrs[:valid_pdf] = response.success?
- attrs[:sha256] = response.success? ? Digest::SHA256.hexdigest(response.body) : nil
- attrs[:url] = url
-
- send_slack_notifications(form, attrs, response.headers['Content-Type'])
-
- attrs
- else
- raise FormFetchError, "The form could not be fetched from the url provided. Response code: #{response.status}"
- end
- end
-
- # Given a form +url+, makes a request for the form and returns the response
- def fetch_form(url)
- connection = Faraday.new(url) do |conn|
- conn.use Faraday::FollowRedirects::Middleware
- conn.options.open_timeout = 10
- conn.options.timeout = 30
- conn.adapter Faraday.default_adapter
- end
- connection.get
- end
-
- # Given a +existing_form+ instance of +VAForms::Form+, +updated_attributes+ for that form, and the +content_type+
- # returned by the form URL, sends appropriate Slack notifications
- def send_slack_notifications(existing_form, updated_attrs, content_type)
- if existing_form.valid_pdf && !updated_attrs[:valid_pdf]
- # If the PDF was valid but is no longer valid, notify
- notify_slack(
- "URL for form #{updated_attrs[:form_name]} no longer returns a valid PDF or web page.",
- form_url: updated_attrs[:url]
- )
- elsif existing_form.url != updated_attrs[:url]
- # If the URL has changed, we notify regardless of content
- notify_slack(
- "Form #{updated_attrs[:form_name]} has been updated.",
- from_form_url: existing_form.url,
- to_form_url: updated_attrs[:url]
- )
- elsif existing_form.sha256 != updated_attrs[:sha256] && content_type == 'application/pdf'
- # If sha256 has changed, only notify if the URL actually returns a PDF, because if the URL is for a download
- # web page instead, the change in sha256 may not actually reflect a change in the PDF itself
- notify_slack("PDF contents of form #{updated_attrs[:form_name]} have been updated.")
- end
- end
-
- # Finds or initializes a matching +VAForms::Form+ based on the given +form_data+ as returned from the graphql query
- def find_or_initialize_form(form_data)
- VAForms::Form.find_or_initialize_by(row_id: form_data['fieldVaFormRowId'])
- end
-
- # Returns a hash of attributes for a +VAForms::Form+ record based on the given +form_data+
- # rubocop:disable Metrics/MethodLength
- def gather_form_attributes(form_data)
- attrs = {
- form_name: form_data['fieldVaFormNumber'],
- title: form_data['fieldVaFormName'],
- pages: form_data['fieldVaFormNumPages'],
- language: form_data['fieldVaFormLanguage'].presence || 'en',
- form_type: form_data['fieldVaFormType'],
- form_usage: form_data.dig('fieldVaFormUsage', 'processed'),
- form_details_url:
- form_data['entityPublished'] ? "#{VAForms::Form::FORM_BASE_URL}#{form_data.dig('entityUrl', 'path')}" : '',
- form_tool_intro: form_data['fieldVaFormToolIntro'],
- form_tool_url: form_data['entityPublished'] ? form_data.dig('fieldVaFormToolUrl', 'uri') : '',
- deleted_at: form_data.dig('fieldVaFormDeletedDate', 'value'),
- related_forms: form_data['fieldVaFormRelatedForms'].map { |f| f.dig('entity', 'fieldVaFormNumber') },
- benefit_categories: parse_benefit_categories(form_data),
- va_form_administration: form_data.dig('fieldVaFormAdministration', 'entity', 'entityLabel'),
- **parse_form_revision_dates(form_data)
- }
-
- if (raw_tags = form_data['fieldVaFormNumber'])
- attrs[:tags] = VAForms::RegexHelper.new.strip_va(raw_tags)
- end
-
- attrs
- end
- # rubocop:enable Metrics/MethodLength
-
- # Parses a given date string and returns it in MM-YYYY or YYYY-MM-DD format
- def parse_date(date_string)
- Date.strptime(date_string, date_string.split('-').count == 2 ? '%m-%Y' : '%Y-%m-%d')
- end
-
- # Parses values for +first_issued_on+ and +last_revision_on+ dates from the given +form_data+
- def parse_form_revision_dates(form_data)
- dates = {}
-
- if (issued_string = form_data.dig('fieldVaFormIssueDate', 'value'))
- dates[:first_issued_on] = parse_date(issued_string)
- end
-
- if (revision_string = form_data.dig('fieldVaFormRevisionDate', 'value'))
- dates[:last_revision_on] = parse_date(revision_string)
- end
-
- dates
- end
-
- # Parses an array of valid category name & description hashes from the given +form_data+
- def parse_benefit_categories(form_data)
- form_data['fieldBenefitCategories'].map do |field|
- {
- name: field.dig('entity', 'fieldHomePageHubLabel'),
- description: field.dig('entity', 'entityLabel')
- }
- end
- end
-
- def notify_slack(message, **)
- VAForms::Slack::Messenger.new({ class: self.class.name, message:, ** }).notify!
- rescue => e
- Rails.logger.error("#{self.class.name} failed to notify Slack, message: #{message}", e)
- end
- end
-end
diff --git a/modules/va_forms/app/sidekiq/va_forms/form_reloader.rb b/modules/va_forms/app/sidekiq/va_forms/form_reloader.rb
deleted file mode 100644
index 8c58db199aa..00000000000
--- a/modules/va_forms/app/sidekiq/va_forms/form_reloader.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-# frozen_string_literal: true
-
-require 'sidekiq'
-
-module VAForms
- class FormReloader
- include Sidekiq::Job
-
- sidekiq_options retry: 7
-
- STATSD_KEY_PREFIX = 'api.va_forms.form_reloader'
-
- sidekiq_retries_exhausted do |msg, _ex|
- job_id = msg['jid']
- job_class = msg['class']
- error_class = msg['error_class']
- error_message = msg['error_message']
-
- StatsD.increment("#{STATSD_KEY_PREFIX}.exhausted")
-
- message = "#{job_class} retries exhausted"
- Rails.logger.error(message, { job_id:, error_class:, error_message: })
- VAForms::Slack::Messenger.new(
- {
- class: job_class.to_s,
- exception: error_class,
- exception_message: error_message,
- detail: message
- }
- ).notify!
- rescue => e
- message = "Failure in #{job_class}#sidekiq_retries_exhausted"
- Rails.logger.error(
- message,
- {
- messaged_content: e.message,
- job_id:,
- pre_exhaustion_failure: {
- error_class:,
- error_message:
- }
- }
- )
- VAForms::Slack::Messenger.new(
- {
- class: job_class.to_s,
- exception: e.class.to_s,
- exception_message: e.message,
- detail: message
- }
- ).notify!
-
- raise e
- end
-
- def perform
- return unless enabled?
-
- all_forms_data.each { |form| VAForms::FormBuilder.perform_async(form) }
-
- # append new tags for pg_search
- VAForms::UpdateFormTagsService.run
- end
-
- def all_forms_data
- query = File.read(Rails.root.join('modules', 'va_forms', 'config', 'graphql_query.txt'))
- body = { query: }
- response = connection.post do |req|
- req.path = 'graphql'
- req.body = body.to_json
- req.options.timeout = 300
- end
- JSON.parse(response.body).dig('data', 'nodeQuery', 'entities')
- end
-
- def connection
- @connection ||= Faraday.new(Settings.va_forms.drupal_url, faraday_options) do |faraday|
- faraday.request :url_encoded
- faraday.request :authorization, :basic, Settings.va_forms.drupal_username, Settings.va_forms.drupal_password
- faraday.adapter faraday_adapter
- end
- end
-
- def faraday_adapter
- Rails.env.production? ? Faraday.default_adapter : :net_http_socks
- end
-
- def faraday_options
- options = { ssl: { verify: false } }
- options[:proxy] = { uri: URI.parse('socks://localhost:2001') } unless Rails.env.production?
- options
- end
-
- private
-
- def enabled?
- Settings.va_forms.form_reloader.enabled
- end
- end
-end
diff --git a/modules/va_forms/app/swagger/va_forms/forms/form_swagger.rb b/modules/va_forms/app/swagger/va_forms/forms/form_swagger.rb
deleted file mode 100644
index 7f2fb57063d..00000000000
--- a/modules/va_forms/app/swagger/va_forms/forms/form_swagger.rb
+++ /dev/null
@@ -1,314 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- module Forms
- class FormSwagger
- include Swagger::Blocks
-
- swagger_component do
- schema :FormsIndex do
- key :description, I18n.t('va_forms.endpoint_descriptions.index')
- property :id do
- key :description, 'JSON API identifier'
- key :type, :string
- key :example, '5403'
- end
- property :type do
- key :description, 'JSON API type specification'
- key :type, :string
- key :example, 'va_form'
- end
- property :attributes do
- property :form_name do
- key :description, I18n.t('va_forms.field_descriptions.form_name')
- key :type, :string
- key :example, '10-10EZ'
- end
- property :url do
- key :description, I18n.t('va_forms.field_descriptions.url')
- key :type, :string
- key :example, 'https://www.va.gov/vaforms/medical/pdf/10-10EZ-fillable.pdf'
- end
- property :title do
- key :description, I18n.t('va_forms.field_descriptions.title')
- key :type, :string
- key :example, 'Instructions and Enrollment Application for Health Benefits'
- end
- property :first_issued_on do
- key :description, I18n.t('va_forms.field_descriptions.first_issued_on')
- key :type, :string
- key :nullable, true
- key :example, '2016-07-10'
- key :format, 'date'
- end
- property :last_revision_on do
- key :description, I18n.t('va_forms.field_descriptions.last_revision_on')
- key :type, :string
- key :nullable, true
- key :example, '2020-01-17'
- key :format, 'date'
- end
- property :pages do
- key :description, I18n.t('va_forms.field_descriptions.pages')
- key :type, :integer
- key :example, 5
- end
- property :sha256 do
- key :description, I18n.t('va_forms.field_descriptions.sha256')
- key :type, :string
- key :nullable, true
- key :example, '6e6465e2e1c89225871daa9b6d86b92d1c263c7b02f98541212af7b35272372b'
- end
- property :last_sha256_change do
- key :description, I18n.t('va_forms.field_descriptions.last_sha256_change')
- key :type, :string
- key :nullable, true
- key :example, '2019-05-30'
- key :format, 'date'
- end
- property :valid_pdf do
- key :description, I18n.t('va_forms.field_descriptions.valid_pdf')
- key :type, :boolean
- key :example, 'true'
- end
- property :form_usage do
- key :description, I18n.t('va_forms.field_descriptions.form_usage')
- key :type, :string
- key :nullable, true
- key :example, '
Use VA Form 10-10EZ if you’re a Veteran and want to apply for VA health care. You must be enrolled in...
'
- end
- property :form_tool_intro do
- key :description, I18n.t('va_forms.field_descriptions.form_tool_intro')
- key :type, :string
- key :nullable, true
- key :example, 'You can apply online instead of filling out and sending us the paper form.'
- end
- property :form_tool_url do
- key :description, I18n.t('va_forms.field_descriptions.form_tool_url')
- key :type, :string
- key :nullable, true
- key :example, 'https://www.va.gov/health-care/apply/application/introduction'
- end
- property :form_details_url do
- key :description, I18n.t('va_forms.field_descriptions.form_details_url')
- key :type, :string
- key :nullable, true
- key :example, 'https://www.va.gov/find-forms/about-form-10-10ez'
- end
- property :form_type do
- key :description, I18n.t('va_forms.field_descriptions.form_type')
- key :type, :string
- key :nullable, true
- key :example, 'benefit'
- end
- property :language do
- key :description, I18n.t('va_forms.field_descriptions.language')
- key :type, :string
- key :example, 'en'
- end
- property :deleted_at do
- key :description, I18n.t('va_forms.field_descriptions.deleted_at')
- key :type, :string
- key :nullable, true
- key :example, 'null'
- key :format, 'date-time'
- end
- property :related_forms do
- key :description, I18n.t('va_forms.field_descriptions.related_forms')
- key :type, :array
- key :nullable, true
- items do
- key :type, :string
- key :example, '10-10EZR'
- end
- end
- property :benefit_categories do
- key :description, I18n.t('va_forms.field_descriptions.benefit_categories')
- key :type, :array
- key :nullable, true
- items do
- property :name do
- key :description, I18n.t('va_forms.field_descriptions.benefit_category_name')
- key :type, :string
- key :example, 'Health care'
- end
- property :description do
- key :description, I18n.t('va_forms.field_descriptions.benefit_category_description')
- key :type, :string
- key :example, 'VA health care'
- end
- end
- end
- property :va_form_administration do
- key :description, I18n.t('va_forms.field_descriptions.va_form_administration')
- key :type, :string
- key :nullable, true
- key :example, 'Veterans Health Administration'
- end
- end
- end
-
- schema :FormShow do
- key :description, I18n.t('va_forms.endpoint_descriptions.show')
- property :id do
- key :description, 'JSON API identifier'
- key :type, :string
- key :example, '10-10-EZ'
- end
- property :type do
- key :description, 'JSON API type specification'
- key :type, :string
- key :example, 'va_form'
- end
- property :attributes do
- property :form_name do
- key :description, I18n.t('va_forms.field_descriptions.form_name')
- key :type, :string
- key :example, '10-10EZ'
- end
- property :url do
- key :description, I18n.t('va_forms.field_descriptions.url')
- key :type, :string
- key :example, 'https://www.va.gov/vaforms/medical/pdf/10-10EZ-fillable.pdf'
- end
- property :title do
- key :description, I18n.t('va_forms.field_descriptions.title')
- key :type, :string
- key :example, 'Instructions and Enrollment Application for Health Benefits'
- end
- property :first_issued_on do
- key :description, I18n.t('va_forms.field_descriptions.first_issued_on')
- key :type, :string
- key :nullable, true
- key :example, '2016-07-10'
- key :format, 'date'
- end
- property :last_revision_on do
- key :description, I18n.t('va_forms.field_descriptions.last_revision_on')
- key :type, :string
- key :nullable, true
- key :example, '2020-01-17'
- key :format, 'date'
- end
- property :created_at do
- key :description, I18n.t('va_forms.field_descriptions.created_at')
- key :type, :string
- key :nullable, true
- key :example, '2021-03-30T16:28:30.338Z'
- key :format, 'date-time'
- end
- property :pages do
- key :description, I18n.t('va_forms.field_descriptions.pages')
- key :type, :integer
- key :example, 5
- end
- property :sha256 do
- key :description, I18n.t('va_forms.field_descriptions.sha256')
- key :type, :string
- key :nullable, true
- key :example, '5fe171299ece147e8b456961a38e17f1391026f26e9e170229317bc95d9827b7'
- end
- property :valid_pdf do
- key :description, I18n.t('va_forms.field_descriptions.valid_pdf')
- key :type, :boolean
- key :example, 'true'
- end
- property :form_usage do
- key :description, I18n.t('va_forms.field_descriptions.form_usage')
- key :type, :string
- key :nullable, true
- key :example, '
Use VA Form 10-10EZ if you’re a Veteran and want to apply for VA health care. You must be enrolled in...
'
- end
- property :form_tool_intro do
- key :description, I18n.t('va_forms.field_descriptions.form_tool_intro')
- key :type, :string
- key :nullable, true
- key :example, 'You can apply online instead of filling out and sending us the paper form.'
- end
- property :form_tool_url do
- key :description, I18n.t('va_forms.field_descriptions.form_tool_url')
- key :type, :string
- key :nullable, true
- key :example, 'https://www.va.gov/health-care/apply/application/introduction'
- end
- property :form_details_url do
- key :description, I18n.t('va_forms.field_descriptions.form_details_url')
- key :type, :string
- key :nullable, true
- key :example, 'https://www.va.gov/find-forms/about-form-10-10ez'
- end
- property :form_type do
- key :description, I18n.t('va_forms.field_descriptions.form_type')
- key :type, :string
- key :nullable, true
- key :example, 'benefit'
- end
- property :language do
- key :description, I18n.t('va_forms.field_descriptions.language')
- key :type, :string
- key :nullable, true
- key :example, 'en'
- end
- property :deleted_at do
- key :description, I18n.t('va_forms.field_descriptions.deleted_at')
- key :nullable, true
- key :type, :string
- key :example, nil
- key :format, 'date-time'
- end
- property :related_forms do
- key :description, I18n.t('va_forms.field_descriptions.related_forms')
- key :type, :array
- key :nullable, true
- items do
- key :type, :string
- key :example, '10-10EZR'
- end
- end
- property :benefit_categories do
- key :description, I18n.t('va_forms.field_descriptions.benefit_categories')
- key :type, :array
- key :nullable, true
- items do
- property :name do
- key :description, I18n.t('va_forms.field_descriptions.benefit_category_name')
- key :type, :string
- key :example, 'Health care'
- end
- property :description do
- key :description, I18n.t('va_forms.field_descriptions.benefit_category_description')
- key :type, :string
- key :example, 'VA health care'
- end
- end
- end
- property :va_form_administration do
- key :description, I18n.t('va_forms.field_descriptions.va_form_administration')
- key :type, :string
- key :nullable, true
- key :example, 'Veterans Health Administration'
- end
- property :versions do
- key :type, :array
- key :nullable, true
- key :description, I18n.t('va_forms.field_descriptions.versions')
- items do
- property :sha256 do
- key :description, I18n.t('va_forms.field_descriptions.version_sha256')
- key :type, :string
- key :example, '5fe171299ece147e8b456961a38e17f1391026f26e9e170229317bc95d9827b7'
- end
- property :revision_on do
- key :description, I18n.t('va_forms.field_descriptions.version_revised_on')
- key :type, :string
- key :example, '2012-01-01'
- key :format, 'date'
- end
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/modules/va_forms/app/swagger/va_forms/v0/controller_swagger.rb b/modules/va_forms/app/swagger/va_forms/v0/controller_swagger.rb
deleted file mode 100644
index 27ad0dade28..00000000000
--- a/modules/va_forms/app/swagger/va_forms/v0/controller_swagger.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- module V0
- class ControllerSwagger
- include Swagger::Blocks
-
- swagger_path '/forms' do
- operation :get do
- security do
- key :apikey, []
- end
- key :summary, 'Returns all VA Forms and their last revision date'
- key :description, 'Returns an index of all available VA forms. Optionally, pass a query parameter to filter forms by form number or title.'
- key :operationId, 'findForms'
- key :produces, [
- 'application/json'
- ]
- key :tags, [
- 'Forms'
- ]
-
- parameter do
- key :name, :query
- key :in, :query
- key :description, 'Returns form data based on entered form name.'
- key :required, false
- key :type, :string
- end
-
- response 200 do
- key :description, 'VA Forms index response'
- content 'application/json' do
- schema do
- key :type, :object
- key :required, [:data]
- property :data do
- key :type, :array
- items do
- key :$ref, :FormsIndex
- end
- end
- end
- end
- end
-
- response 401 do
- key :description, 'Unauthorized'
- content 'application/json' do
- schema do
- property :message do
- key :type, :string
- key :example, 'Invalid authentication credentials'
- end
- end
- end
- end
-
- response 429 do
- key :description, 'Too many requests'
- content 'application/json' do
- schema do
- property :message do
- key :type, :string
- key :example, 'API rate limit exceeded'
- end
- end
- end
- end
- end
- end
-
- swagger_path '/forms/{form_name}' do
- operation :get do
- security do
- key :apikey, []
- end
- key :summary, 'Find form by form name'
- key :description, 'Returns a single form and the full revision history'
- key :operationId, 'findFormByFormName'
- key :tags, [
- 'Forms'
- ]
- parameter do
- key :name, :form_name
- key :in, :path
- key :description, 'The VA form_name of the form being requested. The exact form name must be passed, including proper placement of prefixes and/or hyphens.'
- key :required, true
- key :example, '10-10EZ'
- schema do
- key :type, :string
- end
- end
-
- response 200 do
- key :description, 'VA Form Show response'
- content 'application/json' do
- schema do
- key :type, :object
- key :required, [:data]
- property :data do
- key :$ref, :FormShow
- end
- end
- end
- end
-
- response 401 do
- key :description, 'Unauthorized'
- content 'application/json' do
- schema do
- property :message do
- key :type, :string
- key :example, 'Invalid authentication credentials'
- end
- end
- end
- end
-
- response 404 do
- key :description, 'Not Found'
- content 'application/json' do
- schema do
- key :type, :object
- key :required, [:errors]
- property :errors do
- key :type, :array
- items do
- property :message do
- key :type, :string
- key :example, 'Form not found'
- end
- end
- end
- end
- end
- end
-
- response 429 do
- key :description, 'Too many requests'
- content 'application/json' do
- schema do
- property :message do
- key :type, :string
- key :example, 'API rate limit exceeded'
- end
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/modules/va_forms/app/swagger/va_forms/v0/description.md b/modules/va_forms/app/swagger/va_forms/v0/description.md
deleted file mode 100644
index c10c2fa9c29..00000000000
--- a/modules/va_forms/app/swagger/va_forms/v0/description.md
+++ /dev/null
@@ -1,29 +0,0 @@
-Use the VA Forms API to search for VA forms, get the form's PDF link and metadata, and check for new versions.
-
-Visit our VA Lighthouse [Contact Us page](https://developer.va.gov/support) for further assistance.
-
-## Background
-This API offers an efficient way to stay up-to-date with the latest VA forms and information. The forms information listed on VA.gov matches the information returned by this API.
-- Search by form number, keyword, or title
-- Get a link to the form in PDF format
-- Get detailed form metadata including the number of pages, related forms, benefit categories, language, and more
-- Retrieve the latest date of PDF changes and the SHA256 checksum
-- Identify when a form is deleted by the VA
-
-## Technical summary
-The VA Forms API collects form data from the official VA Form Repository on a nightly basis. The Index endpoint can return all available forms or, if an optional query parameter is passed, will return only forms that may relate to the query value. When a valid form name is passed to the Show endpoint, it will return a single form with additional metadata and full revision history. A JSON response is given with the PDF link (if published) and the corresponding form metadata.
-
-### Authentication and authorization
-The form information shared by this API is publicly available. API requests are authorized through a symmetric API token, provided in an HTTP header with name apikey. [Get a sandbox API Key](https://developer.va.gov/explore/api/va-forms/sandbox-access).
-
-### Testing in sandbox environment
-Form data in the sandbox environment is for testing your API only, and is not guaranteed to be up-to-date. This API also has a reduced API rate limit. When you're ready to move to production, be sure to [request a production API key.](https://developer.va.gov/go-live)
-
-### SHA256 revision history
-Each form is checked nightly for recent file changes. A corresponding SHA256 checksum is calculated, which provides a record of when the PDF changed and the SHA256 hash that was calculated. This allows end users to know that they have the most recent version and can verify the integrity of a previously downloaded PDF.
-
-### Valid PDF link
-Additionally, during the nightly refresh process, the link to the form PDF is verified and the `valid_pdf` metadata is updated accordingly. If marked `true`, the link is valid and is a current form. If marked `false`, the link is either broken or the form has been removed.
-
-### Deleted forms
-If the `deleted_at` metadata is set, that means the VA has removed this form from the repository and it is no longer to be used.
diff --git a/modules/va_forms/app/swagger/va_forms/v0/security_scheme_swagger.rb b/modules/va_forms/app/swagger/va_forms/v0/security_scheme_swagger.rb
deleted file mode 100644
index b8b361495ac..00000000000
--- a/modules/va_forms/app/swagger/va_forms/v0/security_scheme_swagger.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- module V0
- class SecuritySchemeSwagger
- include Swagger::Blocks
- swagger_component do
- security_scheme :apikey do
- key :type, :apiKey
- key :name, :apikey
- key :in, :header
- end
- end
- end
- end
-end
diff --git a/modules/va_forms/app/swagger/va_forms/v0/swagger_root.rb b/modules/va_forms/app/swagger/va_forms/v0/swagger_root.rb
deleted file mode 100644
index 04559a79964..00000000000
--- a/modules/va_forms/app/swagger/va_forms/v0/swagger_root.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- module V0
- class SwaggerRoot
- include Swagger::Blocks
- swagger_root do
- key :openapi, '3.0.0'
- info do
- key :version, '0.0.0'
- key :title, 'VA Forms'
- key :description, File.read(VAForms::Engine.root.join('app', 'swagger', 'va_forms', 'v0', 'description.md'))
- contact do
- key :name, 'va.gov'
- end
- end
-
- server do
- key :url, 'https://sandbox-api.va.gov/services/va_forms/{version}'
- key :description, 'VA.gov API sandbox environment'
- variable :version do
- key :default, 'v0'
- end
- end
-
- server do
- key :url, 'https://api.va.gov/services/va_forms/{version}'
- key :description, 'VA.gov API production environment'
- variable :version do
- key :default, 'v0'
- end
- end
-
- key :basePath, '/services/va_forms/v0'
- key :consumes, ['application/json']
- key :produces, ['application/json']
- end
- end
- end
-end
diff --git a/modules/va_forms/bin/rails b/modules/va_forms/bin/rails
deleted file mode 100755
index 183366fe15c..00000000000
--- a/modules/va_forms/bin/rails
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-ENGINE_ROOT = File.expand_path('..', __dir__)
-ENGINE_PATH = File.expand_path('../lib/vba_documents/engine', __dir__)
-
-# Set up gems listed in the Gemfile.
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
-require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
-
-require 'rails/all'
-require 'rails/engine/commands'
diff --git a/modules/va_forms/catalog-info.yaml b/modules/va_forms/catalog-info.yaml
deleted file mode 100644
index b7724bcb974..00000000000
--- a/modules/va_forms/catalog-info.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-apiVersion: backstage.io/v1alpha1
-kind: API
-metadata:
- name: va_forms
- description: The VA Forms API makes available the latest version of every VA form. Consumers of this API can search by form number, keyword, or title, get a link to the form in PDF format, get detailed form metadata, find the latest date of PDF changes and the SHA256 checksum, and identify when a form is deleted by the VA.
- title: VA Forms API
- annotations:
- datadog/sandbox-monitor-id: 130870
- datadog/production-monitor-id: 130899
- tags:
- - forms
- - ruby
- - lighthouse
- - external
- links:
- - url: https://developer.va.gov/explore/api/va-forms
- title: Public Docs
- icon: web
-spec:
- type: openapi
- lifecycle: production
- owner: lighthouse-banana-peels
- system: Lighthouse Benefits Application Programming Interfaces
- definition:
- $text: https://dev-api.va.gov/services/va_forms/docs/v0/api
diff --git a/modules/va_forms/config/flipper/enabled_features.yml b/modules/va_forms/config/flipper/enabled_features.yml
deleted file mode 100644
index 4664d7ba43b..00000000000
--- a/modules/va_forms/config/flipper/enabled_features.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-common:
-development:
-staging:
-sandbox:
-production:
diff --git a/modules/va_forms/config/graphql_list.txt b/modules/va_forms/config/graphql_list.txt
deleted file mode 100644
index ebe354e2b8f..00000000000
--- a/modules/va_forms/config/graphql_list.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- nodeQuery(limit: 1000, offset: 0, filter: {conditions: [{field: "type", value: ["va_form"]}]}) {
- entities {
- ... on NodePage {
- fieldRelatedLinks {
- entity {
- parentFieldName
- }
- }
- }
- ...vaForm
- }
- }
-}
-fragment vaForm on NodeVaForm {
- fieldVaFormRowId
-}
diff --git a/modules/va_forms/config/graphql_query.txt b/modules/va_forms/config/graphql_query.txt
deleted file mode 100644
index ab68847f60e..00000000000
--- a/modules/va_forms/config/graphql_query.txt
+++ /dev/null
@@ -1,117 +0,0 @@
-{
- nodeQuery(limit: 1000, offset: 0, filter: {conditions: [{field: "type", value: ["va_form"]}]}) {
- entities {
- ... on NodePage {
- fieldRelatedLinks {
- entity {
- parentFieldName
- }
- }
- }
- ...vaForm
- }
- }
-}
-fragment vaForm on NodeVaForm {
- fieldVaFormNumber
- fieldVaFormRowId
- entityBundle
- entityId
- entityPublished
- entityUrl {
- path
- }
- entityTranslations {
- entityCreated
- entityLabel
- entityId
- entityChanged
- entityBundle
- entityType
- entityUuid
- }
- entityRevisions {
- entities {
- entityChanged
- ... on NodeVaForm {
- fieldVaFormName
- }
- }
- }
- title
- status
- revisionLog
- fieldVaFormDeleted
- fieldVaFormDeletedDate {
- value
- }
- fieldVaFormLanguage
- title
- fieldVaFormName
- fieldVaFormTitle
- fieldVaFormType
- fieldVaFormUrl {
- uri
- }
- fieldVaFormUsage {
- value
- format
- processed
- }
- fieldVaFormToolIntro
- fieldVaFormToolUrl {
- uri
- title
- options
- }
- fieldBenefitCategories {
- targetId
- entity {
- entityLabel
- ... on NodeLandingPage {
- fieldHomePageHubLabel
- }
- }
- }
- fieldVaFormRevisionDate {
- value
- date
- }
- fieldVaFormIssueDate {
- value
- date
- }
- fieldVaFormNumPages
-
- fieldVaFormLinkTeasers {
- entity {
- entityLabel
- parentFieldName
- ... on ParagraphLinkTeaser {
- entityId
- fieldLink {
- url {
- path
- }
- title
- options
- }
- fieldLinkSummary
- }
- }
- }
- fieldVaFormRelatedForms {
- entity {
- ... on NodeVaForm {
- fieldVaFormNumber
- }
- }
- }
- fieldVaFormAdministration {
- entity {
- entityLabel
- }
- }
- changed
- status
-}
diff --git a/modules/va_forms/config/locales/en.yml b/modules/va_forms/config/locales/en.yml
deleted file mode 100644
index 637f1fd0f20..00000000000
--- a/modules/va_forms/config/locales/en.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-en:
- va_forms:
- endpoint_descriptions:
- index: 'A listing of available VA forms and their location.'
- show: 'Data for a particular VA form, including form version history.'
- field_descriptions:
- benefit_categories: 'Listing of benefit categories and match'
- benefit_category_name: 'Name of the benefit category of the form'
- benefit_category_description: 'Description of the benefit category of the form'
- va_form_administration: 'The VA organization that administers the form'
- deleted_at: "The timestamp at which the form was deleted"
- created_at: "Internal field for VA.gov use"
- first_issued_on: 'The date the form first became available'
- form_name: 'Name of the VA Form'
- form_tool_intro: 'Introductory text describing the VA online tool for this form'
- form_tool_url: 'Location of the online tool for this form'
- form_details_url: 'Location on www.va.gov of the info page for this form'
- form_type: 'VA Type of the form'
- form_usage: 'A description of how the form is to be used'
- language: 'Language code of the form'
- last_revision_on: 'The date the form was last updated'
- pages: 'Number of pages contained in the form'
- related_forms: 'A listing of other forms that relate to current form'
- sha256: 'A sha256 hash of the form contents'
- last_sha256_change: 'The date of the last sha256 hash change'
- title: 'Title of the form as given by VA'
- url: 'Web location of the form'
- valid_pdf: 'A flag indicating whether the form url was confirmed as a valid download'
- version_sha256: 'A sha256 hash of the form contents for that version'
- version_revised_on: 'The date the sha256 hash was calculated'
- versions: 'The version history of revisions to the form'
diff --git a/modules/va_forms/config/routes.rb b/modules/va_forms/config/routes.rb
deleted file mode 100644
index 7581ca4fb5a..00000000000
--- a/modules/va_forms/config/routes.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-VAForms::Engine.routes.draw do
- get '/metadata', to: 'metadata#index'
- match '/v0/*path', to: 'application#cors_preflight', via: [:options]
- get '/v0/healthcheck', to: 'metadata#healthcheck'
- get '/v0/upstream_healthcheck', to: 'metadata#upstream_healthcheck'
-
- namespace :v0, defaults: { format: 'json' } do
- resources :forms, only: %i[index show]
- end
- namespace :docs do
- namespace :v0 do
- get 'api', to: 'api#index'
- end
- end
-end
diff --git a/modules/va_forms/config/test.txt b/modules/va_forms/config/test.txt
deleted file mode 100644
index 8506f314d30..00000000000
--- a/modules/va_forms/config/test.txt
+++ /dev/null
@@ -1,117 +0,0 @@
-{
- nodeQuery(limit: 1000, offset: 0, filter: {conditions: [{ field: "field_va_form_number", value: "21P-530EZ", operator: LIKE }]}) {
- entities {
- ... on NodePage {
- fieldRelatedLinks {
- entity {
- parentFieldName
- }
- }
- }
- ...vaForm
- }
- }
-}
-fragment vaForm on NodeVaForm {
- fieldVaFormNumber
- fieldVaFormRowId
- entityBundle
- entityId
- entityPublished
- entityUrl {
- path
- }
- entityTranslations {
- entityCreated
- entityLabel
- entityId
- entityChanged
- entityBundle
- entityType
- entityUuid
- }
- entityRevisions {
- entities {
- entityChanged
- ... on NodeVaForm {
- fieldVaFormName
- }
- }
- }
- title
- status
- revisionLog
- fieldVaFormDeleted
- fieldVaFormDeletedDate {
- value
- }
- fieldVaFormLanguage
- title
- fieldVaFormName
- fieldVaFormTitle
- fieldVaFormType
- fieldVaFormUrl {
- uri
- }
- fieldVaFormUsage {
- value
- format
- processed
- }
- fieldVaFormToolIntro
- fieldVaFormToolUrl {
- uri
- title
- options
- }
- fieldBenefitCategories {
- targetId
- entity {
- entityLabel
- ... on NodeLandingPage {
- fieldHomePageHubLabel
- }
- }
- }
- fieldVaFormRevisionDate {
- value
- date
- }
- fieldVaFormIssueDate {
- value
- date
- }
- fieldVaFormNumPages
-
- fieldVaFormLinkTeasers {
- entity {
- entityLabel
- parentFieldName
- ... on ParagraphLinkTeaser {
- entityId
- fieldLink {
- url {
- path
- }
- title
- options
- }
- fieldLinkSummary
- }
- }
- }
- fieldVaFormRelatedForms {
- entity {
- ... on NodeVaForm {
- fieldVaFormNumber
- }
- }
- }
- fieldVaFormAdministration {
- entity {
- entityLabel
- }
- }
- changed
- status
-}
diff --git a/modules/va_forms/config/update_form_tags.yaml b/modules/va_forms/config/update_form_tags.yaml
deleted file mode 100644
index bd21588c746..00000000000
--- a/modules/va_forms/config/update_form_tags.yaml
+++ /dev/null
@@ -1,108 +0,0 @@
----
-form tags list:
-- tags:
- - tdiu
- - iu
- - individual
- - unemployability
- - unemployment
- - application
- - increased
- - compensation
- - occupation
- - hospitalized
- - hospitalization
- - injury
- - service
- - connected
- - disability
- - condition
- - contention
- - self-employment
- - employer
- - claim
- - work
- - income
- form_names:
- - 21-8940
-- tags:
- - coe
- form_names:
- - 26-1880
-- tags:
- - rfs
- form_names:
- - 10-10172
-- tags:
- - roi
- form_names:
- - va0710
- - 10-252
- - 10-0459
- - 10-259
- - 10-5345
- - 10-10116
- - va3288
- - 10-055
- - 10-0527
- - 10-0525a
- - 10-0493
-- tags:
- - poi
- form_names:
- - 10-0137
-- tags:
- - 21-0966
- form_names:
- - itf
-- tags:
- - 21p-535
- form_names:
- - dic
-- tags:
- - rn
- form_names:
- - 10-2850a
- - 10-0430
-- tags:
- - headstone
- form_names:
- - 21p-530
- - va40-10007
- - 21p-10196
-- tags:
- - vehicle
- - car
- form_names:
- - 10-1394
- - 10-2511
-- tags:
- - caretaker
- form_names:
- - 10-10cg
-- tags:
- - dea
- form_names:
- - 22-5490
-- tags:
- - hippa
- form_names:
- - 10-252
- - 10-10163
- - 10-10164
- - 10-0527
- - 10-10116
-- tags:
- - pcafc
- form_names:
- - 10-10cg
-- tags:
- - vr
- - vrne
- form_names:
- - 28-0588
- - 28-1900
-- tags:
- - 526
- form_names:
- - 21-526EZ
diff --git a/modules/va_forms/db/migrate/20200817140442_add_form_details_url_to_va_forms.rb b/modules/va_forms/db/migrate/20200817140442_add_form_details_url_to_va_forms.rb
deleted file mode 100644
index 198dd9a3329..00000000000
--- a/modules/va_forms/db/migrate/20200817140442_add_form_details_url_to_va_forms.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddFormDetailsUrlToVAForms < ActiveRecord::Migration[6.0]
- def change
- add_column :va_forms_forms, :form_details_url, :string
- end
-end
diff --git a/modules/va_forms/lib/tasks/update_form_ranking.rake b/modules/va_forms/lib/tasks/update_form_ranking.rake
deleted file mode 100644
index 30d2f657163..00000000000
--- a/modules/va_forms/lib/tasks/update_form_ranking.rake
+++ /dev/null
@@ -1,105 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- module UpdateFormRanking
- module_function
-
- # rubocop:disable Metrics/MethodLength
- def run
- ActiveRecord::Base.transaction do
- # rubocop:disable Layout/LineLength
- ActiveRecord::Base.connection.execute("
- UPDATE va_forms_forms SET tags ='21-4138F 4138 claim' , ranking=1 WHERE lower(va_forms_forms.form_name)='21-4138';
- UPDATE va_forms_forms SET tags ='21-526ez' , ranking=2 WHERE lower(va_forms_forms.form_name)='21-526ez';
- UPDATE va_forms_forms SET tags ='20-0995' , ranking=3 WHERE lower(va_forms_forms.form_name)='20-0995';
- UPDATE va_forms_forms SET tags ='10-2850c' , ranking=4 WHERE lower(va_forms_forms.form_name)='10-2850c';
- UPDATE va_forms_forms SET tags ='21-2680' , ranking=7 WHERE lower(va_forms_forms.form_name)='21-2680';
- UPDATE va_forms_forms SET tags ='21-686c' , ranking=8 WHERE lower(va_forms_forms.form_name)='21-686c';
- UPDATE va_forms_forms SET tags ='526ez' , ranking=10 WHERE lower(va_forms_forms.form_name)='21-526ez';
- UPDATE va_forms_forms SET tags ='21-22' , ranking=11 WHERE lower(va_forms_forms.form_name)='21-22';
- UPDATE va_forms_forms SET tags ='10-2850a' , ranking=12 WHERE lower(va_forms_forms.form_name)='10-2850a';
- UPDATE va_forms_forms SET tags ='1010 10-10 10-10ez' , ranking=13 WHERE lower(va_forms_forms.form_name)='10-10ez';
- UPDATE va_forms_forms SET tags ='1010 10-10 10-10ez' , ranking=48 WHERE lower(va_forms_forms.form_name)='10-10ez (esp)';
- UPDATE va_forms_forms SET tags ='995' , ranking=14 WHERE lower(va_forms_forms.form_name)='20-0995';
- UPDATE va_forms_forms SET tags ='21-0966' , ranking=15 WHERE lower(va_forms_forms.form_name)='21-0966';
- UPDATE va_forms_forms SET tags ='1258536' , ranking=16 WHERE lower(va_forms_forms.form_name)='1258536';
- UPDATE va_forms_forms SET tags ='21-526' , ranking=18 WHERE lower(va_forms_forms.form_name)='21-526ez';
- UPDATE va_forms_forms SET tags ='10-10172' , ranking=19 WHERE lower(va_forms_forms.form_name)='10-10172';
- UPDATE va_forms_forms SET tags ='21-0845' , ranking=20 WHERE lower(va_forms_forms.form_name)='21-0845';
- UPDATE va_forms_forms SET tags ='21-4142' , ranking=21 WHERE lower(va_forms_forms.form_name)='21-4142';
- UPDATE va_forms_forms SET tags ='20-0996' , ranking=22 WHERE lower(va_forms_forms.form_name)='20-0996';
- UPDATE va_forms_forms SET tags ='21-0781' , ranking=23 WHERE lower(va_forms_forms.form_name)='21-0781';
- UPDATE va_forms_forms SET tags ='21p-534ez' , ranking=24 WHERE lower(va_forms_forms.form_name)='21p-534ez';
- UPDATE va_forms_forms SET tags ='2680' , ranking=25 WHERE lower(va_forms_forms.form_name)='21-2680';
- UPDATE va_forms_forms SET tags ='966' , ranking=27 WHERE lower(va_forms_forms.form_name)='21-0966';
- UPDATE va_forms_forms SET tags ='21p-0969' , ranking=28 WHERE lower(va_forms_forms.form_name)='21p-0969';
- UPDATE va_forms_forms SET tags ='21-8940' , ranking=29 WHERE lower(va_forms_forms.form_name)='21-8940';
- UPDATE va_forms_forms SET tags ='686c' , ranking=30 WHERE lower(va_forms_forms.form_name)='21-686c';
- UPDATE va_forms_forms SET tags ='534' , ranking=31 WHERE lower(va_forms_forms.form_name)='1258536';
- UPDATE va_forms_forms SET tags ='600003' , ranking=32 WHERE lower(va_forms_forms.form_name)='600003';
- UPDATE va_forms_forms SET tags ='10182' , ranking=33 WHERE lower(va_forms_forms.form_name)='va10182';
- UPDATE va_forms_forms SET tags ='845' , ranking=34 WHERE lower(va_forms_forms.form_name)='21-0845';
- UPDATE va_forms_forms SET tags ='21p-530' , ranking=35 WHERE lower(va_forms_forms.form_name)='21p-530';
- UPDATE va_forms_forms SET tags ='4142' , ranking=36 WHERE lower(va_forms_forms.form_name)='21-4142';
- UPDATE va_forms_forms SET tags ='21-674' , ranking=37 WHERE lower(va_forms_forms.form_name)='21-674';
- UPDATE va_forms_forms SET tags ='10-7959c' , ranking=38 WHERE lower(va_forms_forms.form_name)='10-7959c';
- UPDATE va_forms_forms SET tags ='21p-527ez' , ranking=39 WHERE lower(va_forms_forms.form_name)='21p-527ez';
- UPDATE va_forms_forms SET tags ='21-4142a' , ranking=40 WHERE lower(va_forms_forms.form_name)='21-4142a';
- UPDATE va_forms_forms SET tags ='10-5345a' , ranking=41 WHERE lower(va_forms_forms.form_name)='10-5345a';
- UPDATE va_forms_forms SET tags ='26-1880' , ranking=42 WHERE lower(va_forms_forms.form_name)='26-1880';
- UPDATE va_forms_forms SET tags ='22-5490' , ranking=43 WHERE lower(va_forms_forms.form_name)='22-5490';
- UPDATE va_forms_forms SET tags ='1010 10-10 10-10ezr' , ranking=44 WHERE lower(va_forms_forms.form_name)='10-10ezr';
- UPDATE va_forms_forms SET tags ='10-10cg' , ranking=45 WHERE lower(va_forms_forms.form_name)='10-10cg';
- UPDATE va_forms_forms SET tags ='534ez' , ranking=46 WHERE lower(va_forms_forms.form_name)='21p-534ez';
- UPDATE va_forms_forms SET tags ='21p-8416' , ranking=47 WHERE lower(va_forms_forms.form_name)='21p-8416';
- UPDATE va_forms_forms SET tags ='10-10d' , ranking=49 WHERE lower(va_forms_forms.form_name)='10-10d';
- UPDATE va_forms_forms SET tags ='996' , ranking=50 WHERE lower(va_forms_forms.form_name)='20-0996';
- UPDATE va_forms_forms SET tags ='21-4192' , ranking=51 WHERE lower(va_forms_forms.form_name)='21-4192';
- UPDATE va_forms_forms SET tags ='686' , ranking=52 WHERE lower(va_forms_forms.form_name)='21-686c';
- UPDATE va_forms_forms SET tags ='781' , ranking=53 WHERE lower(va_forms_forms.form_name)='21-0781';
- UPDATE va_forms_forms SET tags ='8940' , ranking=54 WHERE lower(va_forms_forms.form_name)='21-8940';
- UPDATE va_forms_forms SET tags ='40-1330' , ranking=55 WHERE lower(va_forms_forms.form_name)='40-1330';
- UPDATE va_forms_forms SET tags ='22-1995' , ranking=56 WHERE lower(va_forms_forms.form_name)='22-1995';
- UPDATE va_forms_forms SET tags ='530' , ranking=57 WHERE lower(va_forms_forms.form_name)='21p-530';
- UPDATE va_forms_forms SET tags ='10-0137' , ranking=58 WHERE lower(va_forms_forms.form_name)='10-0137';
- UPDATE va_forms_forms SET tags ='674' , ranking=59 WHERE lower(va_forms_forms.form_name)='21-674';
- UPDATE va_forms_forms SET tags ='21p-534' , ranking=60 WHERE lower(va_forms_forms.form_name)='21p-534ez';
- UPDATE va_forms_forms SET tags ='5655' , ranking=61 WHERE lower(va_forms_forms.form_name)='va5655';
- UPDATE va_forms_forms SET tags ='21-22a' , ranking=62 WHERE lower(va_forms_forms.form_name)='21-22a';
- UPDATE va_forms_forms SET tags ='21-0779' , ranking=63 WHERE lower(va_forms_forms.form_name)='21-0779';
- UPDATE va_forms_forms SET tags ='2850a' , ranking=64 WHERE lower(va_forms_forms.form_name)='10-2850a';
- UPDATE va_forms_forms SET tags ='21-0538' , ranking=65 WHERE lower(va_forms_forms.form_name)='21-0538';
- UPDATE va_forms_forms SET tags ='of-306' , ranking=66 WHERE lower(va_forms_forms.form_name)='of-306';
- UPDATE va_forms_forms SET tags ='969' , ranking=67 WHERE lower(va_forms_forms.form_name)='21p-0969';
- UPDATE va_forms_forms SET tags ='sf 180' , ranking=68 WHERE lower(va_forms_forms.form_name)='sf180';
- UPDATE va_forms_forms SET tags ='10-8678' , ranking=69 WHERE lower(va_forms_forms.form_name)='10-8678';
- UPDATE va_forms_forms SET tags ='290309 direct deposit' , ranking=1 WHERE lower(va_forms_forms.form_name)='29-0309';
- UPDATE va_forms_forms SET tags= '20572 direct deposit' , ranking=2 WHERE lower(va_forms_forms.form_name)='20-572';
- UPDATE va_forms_forms SET tags= 'SF1199a direct deposit' , ranking=3 WHERE lower(va_forms_forms.form_name)='sf-1199a';
- UPDATE va_forms_forms SET tags= '100459 release of information authorization' , ranking=3 WHERE lower(va_forms_forms.form_name)='10-0459';
- UPDATE va_forms_forms SET tags= '0710 release of information authorization' , ranking=1 WHERE lower(va_forms_forms.form_name)='va0710';
- UPDATE va_forms_forms SET tags= '10252 health release of information authorization' , ranking=2 WHERE lower(va_forms_forms.form_name)='10-252';
- UPDATE va_forms_forms SET tags= '100094f health' , ranking=2 WHERE lower(va_forms_forms.form_name)='10-0094f';
- UPDATE va_forms_forms SET tags= 'supplemental claim' , ranking=14 WHERE lower(va_forms_forms.form_name)='20-0995';
- UPDATE va_forms_forms SET tags= 'advance directive' WHERE lower(va_forms_forms.form_name)='10-0137';
- UPDATE va_forms_forms SET tags= 'advance directive' WHERE lower(va_forms_forms.form_name)='10-0137a';
- UPDATE va_forms_forms SET tags= 'associated health occupations' WHERE lower(va_forms_forms.form_name)='10-2850c';
- UPDATE va_forms_forms SET tags= 'appeal appeal', ranking = 1 WHERE lower(va_forms_forms.form_name)='20-0995';
- UPDATE va_forms_forms SET tags= 'appeal appeal', ranking = 2 WHERE lower(va_forms_forms.form_name)='22-0996';
- UPDATE va_forms_forms SET tags= 'assign a representative appeal', ranking= 4 WHERE lower(va_forms_forms.form_name)='21-22';
- UPDATE va_forms_forms SET tags= 'assign a representative appeal', ranking = 5 WHERE lower(va_forms_forms.form_name)='21-22a';
- UPDATE va_forms_forms SET tags ='appeal va10182' , ranking=3 WHERE lower(va_forms_forms.form_name)='va10182';
- UPDATE va_forms_forms SET tags ='appeal' , ranking=6 WHERE lower(va_forms_forms.form_name)='20-0998';
- ")
- # rubocop:enable Layout/LineLength
- end
- # rubocop:enable Metrics/MethodLength
- end
- end
-end
-
-namespace :va_forms do
- task update_form_ranking: :environment do
- VAForms::UpdateFormRanking.run
- end
-end
diff --git a/modules/va_forms/lib/tasks/update_form_tags.rake b/modules/va_forms/lib/tasks/update_form_tags.rake
deleted file mode 100644
index 9cb385ece24..00000000000
--- a/modules/va_forms/lib/tasks/update_form_tags.rake
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-namespace :va_forms do
- task update_form_tags: :environment do
- VAForms::UpdateFormTagsService.run
- end
-end
diff --git a/modules/va_forms/lib/tasks/va_forms.rake b/modules/va_forms/lib/tasks/va_forms.rake
deleted file mode 100644
index 874d547b2c6..00000000000
--- a/modules/va_forms/lib/tasks/va_forms.rake
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-namespace :va_forms do
- QUERY = File.read(Rails.root.join('modules', 'va_forms', 'config', 'graphql_query.txt'))
- SOCKS_URL = Settings.docker_debugging&.socks_url ? Settings.docker_debugging.socks_url : 'socks://localhost:2001'
- FORMS_URL = Settings.va_forms.drupal_url
- CURL_COMMAND = <<~CURL_COMMAND.freeze
- curl -i -X POST -k -u #{Settings.va_forms.drupal_username}:#{Settings.va_forms.drupal_password} --proxy "#{SOCKS_URL}" -d '#{{ query: QUERY }.to_json}' #{FORMS_URL}/graphql
- CURL_COMMAND
-
- # rubocop:disable Metrics/MethodLength
- # for some strange reason within docker faraday fails over socks.
- def fetch_all_forms
- results, error, exit_code = nil
- puts "starting fetch from #{FORMS_URL}..."
- Open3.popen3(CURL_COMMAND) do |_stdin, stdout, stderr, wait_thr|
- results = stdout.read
- error = stderr.read
- exit_code = wait_thr.value
- end
- results =~ /(\{"data.*)/m
- data = Regexp.last_match(1)
- unless exit_code.success?
- puts "Failed to fetch data from #{FORMS_URL}\n #{error}"
- return
- end
- puts 'Parsing data.'
- forms_data = JSON.parse(data)
- puts 'Populating database, this takes time.'
- num_rows = 0
- forms_data.dig('data', 'nodeQuery', 'entities').each do |form|
- VAForms::FormReloader.new.build_and_save_form(form)
- num_rows += 1
- puts "#{num_rows} completed" if (num_rows % 10).zero?
- rescue => e
- puts "#{form['fieldVaFormNumber']} failed to import into forms database"
- puts e.message
- next
- end
- puts "#{num_rows} added/updated in the database!"
- end
- # rubocop:enable Metrics/MethodLength
-
- task fetch_latest: :environment do
- VAForms::FormReloader.new.perform
- end
-
- task fetch_latest_curl: :environment do
- fetch_all_forms
- end
-end
diff --git a/modules/va_forms/lib/va_forms.rb b/modules/va_forms/lib/va_forms.rb
deleted file mode 100644
index 5d4a6834a50..00000000000
--- a/modules/va_forms/lib/va_forms.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'va_forms/engine'
-
-module VAForms
-end
diff --git a/modules/va_forms/lib/va_forms/engine.rb b/modules/va_forms/lib/va_forms/engine.rb
deleted file mode 100644
index 63043454b14..00000000000
--- a/modules/va_forms/lib/va_forms/engine.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- class Engine < ::Rails::Engine
- isolate_namespace VAForms
-
- initializer :append_migrations do |app|
- unless app.root.to_s.match? root.to_s
- config.paths['db/migrate'].expanded.each do |expanded_path|
- app.config.paths['db/migrate'] << expanded_path
- ActiveRecord::Migrator.migrations_paths << expanded_path
- end
- end
- end
-
- config.generators do |g|
- g.test_framework :rspec, view_specs: false
- g.fixture_replacement :factory_bot
- g.factory_bot dir: 'spec/factories'
- end
-
- initializer 'va_forms.factories', after: 'factory_bot.set_factory_paths' do
- FactoryBot.definition_file_paths << File.expand_path('../../spec/factories', __dir__) if defined?(FactoryBot)
- end
- end
-end
diff --git a/modules/va_forms/lib/va_forms/health_checker.rb b/modules/va_forms/lib/va_forms/health_checker.rb
deleted file mode 100644
index cd51ab9c7f8..00000000000
--- a/modules/va_forms/lib/va_forms/health_checker.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-require 'central_mail/service'
-
-module VAForms
- class HealthChecker
- module Constants
- HEALTH_DESCRIPTION = 'VA Forms API Health Check'
- HEALTH_DESCRIPTION_UPSTREAM = 'VA Forms API Upstream Health Check'
- CMS_SERVICE = 'Content Management System'
- end
- include Constants
-
- SERVICES = [CMS_SERVICE].freeze
-
- def initialize
- @cms_healthy = nil
- end
-
- def services_are_healthy?
- cms_is_healthy?
- end
-
- def healthy_service?(service)
- case service.upcase
-
- when CMS_SERVICE.upcase
- cms_is_healthy?
- else
- raise "VAForms::HealthChecker doesn't recognize #{service}"
- end
- end
-
- private
-
- def cms_is_healthy?
- return @cms_healthy unless @cms_healthy.nil?
-
- @cms_healthy = VAForms::Form.count.positive?
- end
- end
-end
diff --git a/modules/va_forms/lib/va_forms/regex_helper.rb b/modules/va_forms/lib/va_forms/regex_helper.rb
deleted file mode 100644
index 5fecdb5e4c7..00000000000
--- a/modules/va_forms/lib/va_forms/regex_helper.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- class RegexHelper
- def scrub_query(search_term)
- search_term = check_prefix(search_term)
- # Matches 10-10 Forms
- ten_form_regex = /^10\s*10(\s?.*)$/
- # Looks for the common 10 10 and make it 10-10
- if search_term.match(ten_form_regex).present?
- search_term = "10-10#{Regexp.last_match(1)}"
- return search_term
- end
- search_term
- end
-
- def strip_va(number)
- number.gsub(/VA/, '')
- end
-
- private
-
- def check_prefix(search_term)
- # Matches VA/GSA prefixes with or without a space or dash
- va_prefix_regex = /^(?i)(.*)\bva\b(.*)/
- form_form_regex = /^(?i)(.*)\bform\b(.*)/
- if search_term.match(va_prefix_regex).present?
- # Scrub the 'VA' prefix, since not all forms have that, and keep just the number
- search_term = "#{Regexp.last_match(1)}#{Regexp.last_match(2)}"
- search_term = search_term.strip
- search_term = search_term.gsub(/-/, '%')
- end
- if search_term.match(form_form_regex).present?
- # Scrub the 'form' term, since not all forms have that, and keep just the number
- search_term = "#{Regexp.last_match(1)}#{Regexp.last_match(2)}"
- search_term = search_term.strip
- search_term = search_term.gsub(/-/, '%')
- end
- search_term
- end
- end
-end
diff --git a/modules/va_forms/lib/va_forms/version.rb b/modules/va_forms/lib/va_forms/version.rb
deleted file mode 100644
index 79e3caba4b5..00000000000
--- a/modules/va_forms/lib/va_forms/version.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-module VAForms
- VERSION = '0.0.1'
-end
diff --git a/modules/va_forms/postman_tests/Automated Forms API.postman_collection.json b/modules/va_forms/postman_tests/Automated Forms API.postman_collection.json
deleted file mode 100644
index dc1b766883f..00000000000
--- a/modules/va_forms/postman_tests/Automated Forms API.postman_collection.json
+++ /dev/null
@@ -1,151 +0,0 @@
-{
- "info": {
- "_postman_id": "e243c8b1-84fd-4cc0-9a2a-5d185de28ca0",
- "name": "Automated Forms API",
- "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
- },
- "item": [
- {
- "name": "/forms?query",
- "event": [
- {
- "listen": "test",
- "script": {
- "exec": [
- "const jsonData = pm.response.json();\r",
- "pm.environment.set(\"forms_valid_pdf\", \"\");\r",
- "pm.environment.set(\"forms_invalid_pdf\", \"\");\r",
- "\r",
- "pm.test(\"Status code is 200\", function () {\r",
- " pm.response.to.have.status(200);\r",
- "});\r",
- "\r",
- "for(var i = 0; i < jsonData.data.length; i++) {\r",
- " if(jsonData.data[i].attributes.valid_pdf == true && pm.environment.get(\"forms_valid_pdf\") == \"\") {\r",
- " pm.environment.set(\"forms_valid_pdf\", jsonData.data[i].attributes.url);\r",
- " pm.environment.set(\"forms_valid_sha256\", jsonData.data[i].attributes.sha256);\r",
- " }\r",
- "\r",
- " if(jsonData.data[i].attributes.valid_pdf == false && pm.environment.get(\"forms_invalid_pdf\") == \"\") {\r",
- " pm.environment.set(\"forms_invalid_pdf\", jsonData.data[i].attributes.url);\r",
- " }\r",
- "}\r",
- "\r",
- "//if the query didn't get any responses valid or invalid, don't try to hit a url because we don't have one\r",
- "if(pm.environment.get(\"forms_valid_pdf\") == \"\" && pm.environment.get(\"forms_invalid_pdf\") == \"\") {\r",
- " postman.setNextRequest(null);\r",
- "}\r",
- "//if the query didn't return a valid pdf (only an invalid one would have been returned then), skip the valid url request\r",
- "else if(pm.environment.get(\"forms_valid_pdf\") == \"\") {\r",
- " postman.setNextRequest(\"invalid url request\");\r",
- "}\r",
- "//if we have a valid but not an invalid, then we need to skip invalid url request, this logic is handled in the valid url request"
- ],
- "type": "text/javascript"
- }
- }
- ],
- "request": {
- "auth": {
- "type": "apikey",
- "apikey": [
- {
- "key": "key",
- "value": "apikey",
- "type": "string"
- },
- {
- "key": "value",
- "value": "{{apikey}}",
- "type": "string"
- }
- ]
- },
- "method": "GET",
- "header": [],
- "url": {
- "raw": "{{host}}/services/va_forms/v0/forms?query={{forms_query}}",
- "host": [
- "{{host}}"
- ],
- "path": [
- "services",
- "va_forms",
- "v0",
- "forms"
- ],
- "query": [
- {
- "key": "query",
- "value": "{{forms_query}}"
- }
- ]
- }
- },
- "response": []
- },
- {
- "name": "valid url request",
- "event": [
- {
- "listen": "test",
- "script": {
- "exec": [
- "pm.test(\"Status code is 200\", function () {\r",
- " pm.response.to.have.status(200);\r",
- "});\r",
- "\r",
- "pm.test(\"PDF download less than 5 seconds. Actual time: \" + (pm.response.responseTime / 1000) + \" seconds\", function (){\r",
- " pm.expect(pm.response.responseTime).to.be.lessThan(5000);\r",
- "});\r",
- "\r",
- "//if we don't have an invalid pdf to ping, terminate the collection run\r",
- "if(pm.environment.get(\"forms_invalid_pdf\") == \"\") {\r",
- " postman.setNextRequest(null);\r",
- "}"
- ],
- "type": "text/javascript"
- }
- }
- ],
- "request": {
- "method": "GET",
- "header": [],
- "url": {
- "raw": "{{forms_valid_pdf}}",
- "host": [
- "{{forms_valid_pdf}}"
- ]
- }
- },
- "response": []
- },
- {
- "name": "invalid url request",
- "event": [
- {
- "listen": "test",
- "script": {
- "exec": [
- "pm.test(\"Status code is 404\", function () {\r",
- " pm.response.to.have.status(404);\r",
- "});"
- ],
- "type": "text/javascript"
- }
- }
- ],
- "request": {
- "method": "GET",
- "header": [],
- "url": {
- "raw": "{{forms_invalid_pdf}}",
- "host": [
- "{{forms_invalid_pdf}}"
- ]
- }
- },
- "response": []
- }
- ]
-}
\ No newline at end of file
diff --git a/modules/va_forms/postman_tests/Forms_Postman.md b/modules/va_forms/postman_tests/Forms_Postman.md
deleted file mode 100644
index 621d2e66cfc..00000000000
--- a/modules/va_forms/postman_tests/Forms_Postman.md
+++ /dev/null
@@ -1 +0,0 @@
-### Please see the markdown file for [Benefits Intake Postman](https://github.com/department-of-veterans-affairs/vets-api/blob/master/modules/vba_documents/postman_tests/Benefits_Intake_Postman.md)
\ No newline at end of file
diff --git a/modules/va_forms/spec/factories/forms.rb b/modules/va_forms/spec/factories/forms.rb
deleted file mode 100644
index f991ce0645d..00000000000
--- a/modules/va_forms/spec/factories/forms.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :va_form, class: 'VAForms::Form' do
- form_name { '526ez' }
- row_id { 4909 }
- url { 'https://va.gov/va_form/21-526ez.pdf' }
- title { 'Disability Compensation' }
- first_issued_on { Time.zone.today - 1.day }
- last_revision_on { Time.zone.today }
- pages { 2 }
- valid_pdf { true }
- sequence(:sha256) { |n| "abcd#{n}" }
- last_sha256_change { sha256 }
- form_usage { 'Usage description' }
- form_tool_intro { 'Introduction to form tool' }
- form_tool_url { 'https://va.gov/tool' }
- form_details_url { 'https://va.gov/form_details' }
- form_type { 'PDF' }
- language { 'English' }
- related_forms { %w[related_form_1 related_form_2] }
- benefit_categories { %w[benefit_category_1 benefit_category_2] }
- va_form_administration { ['VA Administration'] }
- change_history { { 'versions' => %w[v1 v2] } }
-
- trait :has_been_deleted do
- deleted_at { '2020-07-16T00:00:00.000Z' }
- end
-
- factory :deleted_va_form, parent: :va_form do
- has_been_deleted
- form_name { '528' }
- row_id { 1315 }
- end
- end
-end
diff --git a/modules/va_forms/spec/fixtures/gql_form.json b/modules/va_forms/spec/fixtures/gql_form.json
deleted file mode 100644
index 3c8530981e6..00000000000
--- a/modules/va_forms/spec/fixtures/gql_form.json
+++ /dev/null
@@ -1,131 +0,0 @@
-{
- "entityBundle": "va_form",
- "entityId": "6088",
- "entityPublished": true,
- "entityTranslations": [],
- "entityRevisions": {
- "entities": [
- {
- "entityChanged": "2020-06-22T16:05:57-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-06-22T16:29:49-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-06-24T16:25:05-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-06-25T16:13:33-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-08T14:24:33-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-10T10:22:59-0400",
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-14T12:41:49-0400",
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-16T09:38:11-0400",
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC"
- }
- ]
- },
- "title": "About VA Form 21-0966",
- "status": false,
- "revisionLog": "Shortened the custom meta description: \"Get VA Form 21-0966, Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC. Submitting an intent to file can secure the earliest possible effective date for retroactive payments you may be eligible for.\" 236 chars",
- "fieldVaFormLanguage": "en",
- "entityUrl": {
- "path": "/find-forms/about-form-21-0966"
- },
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC",
- "fieldVaFormTitle": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC",
- "fieldVaFormType": "benefit",
- "fieldVaFormUrl": {
- "uri": "http://www.vba.va.gov/pubs/forms/VBA-21-0966-ARE.pdf",
- "title": null,
- "options": []
- },
- "fieldVaFormUsage": {
- "value": "
Use VA Form 21-0966 if you’re still gathering information to support your claim, and want to start the filing process. Submitting an intent to file can secure the earliest possible effective date for any retroactive payments you may be eligible to receive.
\r\n",
- "format": "rich_text",
- "processed": "Someusagehtml"
- },
- "fieldVaFormNumber": "21-0966",
- "fieldVaFormRowId": 5382,
- "fieldVaFormToolIntro": "some intro text",
- "fieldVaFormToolUrl": {
- "uri": "https://www.va.gov/education/apply-for-education-benefits/application/1995/introduction"
- },
- "fieldBenefitCategories": [
- {
- "targetId": 73,
- "entity": {
- "entityLabel": "VA pension benefits",
- "fieldHomePageHubLabel": "Pension"
- }
- }
- ],
- "fieldVaFormAdministration": {
- "entity": {
- "entityLabel": "Veterans Benefits Administration"
- }
- },
- "fieldVaFormRevisionDate": {
- "value": "2018-08-22",
- "date": "2018-08-22 12:00:00 UTC"
- },
- "fieldVaFormIssueDate": {
- "value": "2019-11-07",
- "date": "2019-11-07 12:00:00 UTC"
- },
- "fieldVaFormNumPages": 1,
- "fieldVaFormLinkTeasers": [
- {
- "entity": {
- "entityLabel": "About VA Form 21-0966 > Helpful links",
- "parentFieldName": "field_va_form_link_teasers",
- "entityId": "10997",
- "fieldLink": {
- "url": {
- "path": "/disability/how-to-file-claim"
- },
- "title": "How to file a VA disability claim",
- "options": []
- },
- "fieldLinkSummary": "Learn about the steps for filing a claim for disability compensation or increased disability compensation. Note: If you file your disability claim online, you don’t need to submit a paper Intent to File form. "
- }
- },
- {
- "entity": {
- "entityLabel": "About VA Form 21-0966 > Helpful links",
- "parentFieldName": "field_va_form_link_teasers",
- "entityId": "10998",
- "fieldLink": {
- "url": {
- "path": "/pension/how-to-apply"
- },
- "title": "How to apply for a VA pension as a Veteran",
- "options": []
- },
- "fieldLinkSummary": "Find out how to apply for tax-free VA pension benefits as a Veteran. Note: If you apply online for pension benefits, you still need to submit VA Form 21-0996 as your intent to file. "
- }
- }
- ],
- "fieldVaFormRelatedForms": [
- {
- "entity": {
- "fieldVaFormNumber": "10-10d"
- }
- }
- ],
- "changed": 1594906691
-}
diff --git a/modules/va_forms/spec/fixtures/gql_form_deleted.json b/modules/va_forms/spec/fixtures/gql_form_deleted.json
deleted file mode 100644
index 4701b3ac9b2..00000000000
--- a/modules/va_forms/spec/fixtures/gql_form_deleted.json
+++ /dev/null
@@ -1,135 +0,0 @@
-{
- "entityBundle": "va_form",
- "entityId": "6088",
- "entityPublished": false,
- "entityTranslations": [],
- "entityRevisions": {
- "entities": [
- {
- "entityChanged": "2020-06-22T16:05:57-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-06-22T16:29:49-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-06-24T16:25:05-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-06-25T16:13:33-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-08T14:24:33-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-10T10:22:59-0400",
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-14T12:41:49-0400",
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-16T09:38:11-0400",
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC"
- }
- ]
- },
- "title": "About VA Form 21-0966",
- "status": false,
- "revisionLog": "Shortened the custom meta description: \"Get VA Form 21-0966, Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC. Submitting an intent to file can secure the earliest possible effective date for retroactive payments you may be eligible for.\" 236 chars",
- "fieldVaFormDeleted": true,
- "fieldVaFormDeletedDate": {
- "value": "2020-07-16"
- },
- "fieldVaFormLanguage": "en",
- "entityUrl": {
- "path": "/find-forms/about-form-21-0966"
- },
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC",
- "fieldVaFormTitle": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC",
- "fieldVaFormType": "benefit",
- "fieldVaFormUrl": {
- "uri": "http://www.vba.va.gov/pubs/forms/VBA-21-0966-ARE.pdf",
- "title": null,
- "options": []
- },
- "fieldVaFormUsage": {
- "value": "
Use VA Form 21-0966 if you’re still gathering information to support your claim, and want to start the filing process. Submitting an intent to file can secure the earliest possible effective date for any retroactive payments you may be eligible to receive.
\r\n",
- "format": "rich_text",
- "processed": "Someusagehtml"
- },
- "fieldVaFormRowId": 5382,
- "fieldVaFormNumber": "21-0966",
- "fieldVaFormToolIntro": "some intro text",
- "fieldVaFormToolUrl": {
- "uri": "https://www.va.gov/education/apply-for-education-benefits/application/1995/introduction"
- },
- "fieldBenefitCategories": [
- {
- "targetId": 73,
- "entity": {
- "entityLabel": "VA pension benefits",
- "fieldHomePageHubLabel": "Pension"
- }
- }
- ],
- "fieldVaFormAdministration": {
- "entity": {
- "entityLabel": "Veterans Benefits Administration"
- }
- },
- "fieldVaFormRevisionDate": {
- "value": "2018-08-22",
- "date": "2018-08-22 12:00:00 UTC"
- },
- "fieldVaFormIssueDate": {
- "value": "2019-11-07",
- "date": "2019-11-07 12:00:00 UTC"
- },
- "fieldVaFormNumPages": 1,
- "fieldVaFormLinkTeasers": [
- {
- "entity": {
- "entityLabel": "About VA Form 21-0966 > Helpful links",
- "parentFieldName": "field_va_form_link_teasers",
- "entityId": "10997",
- "fieldLink": {
- "url": {
- "path": "/disability/how-to-file-claim"
- },
- "title": "How to file a VA disability claim",
- "options": []
- },
- "fieldLinkSummary": "Learn about the steps for filing a claim for disability compensation or increased disability compensation. Note: If you file your disability claim online, you don’t need to submit a paper Intent to File form. "
- }
- },
- {
- "entity": {
- "entityLabel": "About VA Form 21-0966 > Helpful links",
- "parentFieldName": "field_va_form_link_teasers",
- "entityId": "10998",
- "fieldLink": {
- "url": {
- "path": "/pension/how-to-apply"
- },
- "title": "How to apply for a VA pension as a Veteran",
- "options": []
- },
- "fieldLinkSummary": "Find out how to apply for tax-free VA pension benefits as a Veteran. Note: If you apply online for pension benefits, you still need to submit VA Form 21-0996 as your intent to file. "
- }
- }
- ],
- "fieldVaFormRelatedForms": [
- {
- "entity": {
- "fieldVaFormNumber": "10-10d"
- }
- }
- ],
- "changed": 1594906691
-}
diff --git a/modules/va_forms/spec/fixtures/gql_form_invalid_url.json b/modules/va_forms/spec/fixtures/gql_form_invalid_url.json
deleted file mode 100644
index 809cbbae4bd..00000000000
--- a/modules/va_forms/spec/fixtures/gql_form_invalid_url.json
+++ /dev/null
@@ -1,131 +0,0 @@
-{
- "entityBundle": "va_form",
- "entityId": "6088",
- "entityPublished": true,
- "entityTranslations": [],
- "entityRevisions": {
- "entities": [
- {
- "entityChanged": "2020-06-22T16:05:57-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-06-22T16:29:49-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-06-24T16:25:05-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-06-25T16:13:33-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-08T14:24:33-0400",
- "fieldVaFormName": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-10T10:22:59-0400",
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-14T12:41:49-0400",
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC"
- },
- {
- "entityChanged": "2020-07-16T09:38:11-0400",
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC"
- }
- ]
- },
- "title": "About VA Form 21-0966",
- "status": false,
- "revisionLog": "Shortened the custom meta description: \"Get VA Form 21-0966, Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC. Submitting an intent to file can secure the earliest possible effective date for retroactive payments you may be eligible for.\" 236 chars",
- "fieldVaFormLanguage": "en",
- "entityUrl": {
- "path": "/find-forms/about-form-21-0966"
- },
- "fieldVaFormName": "Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC",
- "fieldVaFormTitle": "Intent To File A Claim For Compensation and/or Pension, or Survivors Pension and/or DIC",
- "fieldVaFormType": "benefit",
- "fieldVaFormUrl": {
- "uri": "http://www.vba.va.gov/pubs/forms/not_a_valid_url.pdf",
- "title": null,
- "options": []
- },
- "fieldVaFormUsage": {
- "value": "
Use VA Form 21-0966 if you’re still gathering information to support your claim, and want to start the filing process. Submitting an intent to file can secure the earliest possible effective date for any retroactive payments you may be eligible to receive.
\r\n",
- "format": "rich_text",
- "processed": "Someusagehtml"
- },
- "fieldVaFormNumber": "21-0966",
- "fieldVaFormRowId": 5382,
- "fieldVaFormToolIntro": "some intro text",
- "fieldVaFormToolUrl": {
- "uri": "https://www.va.gov/education/apply-for-education-benefits/application/1995/introduction"
- },
- "fieldBenefitCategories": [
- {
- "targetId": 73,
- "entity": {
- "entityLabel": "VA pension benefits",
- "fieldHomePageHubLabel": "Pension"
- }
- }
- ],
- "fieldVaFormAdministration": {
- "entity": {
- "entityLabel": "Veterans Benefits Administration"
- }
- },
- "fieldVaFormRevisionDate": {
- "value": "2018-08-22",
- "date": "2018-08-22 12:00:00 UTC"
- },
- "fieldVaFormIssueDate": {
- "value": "2019-11-07",
- "date": "2019-11-07 12:00:00 UTC"
- },
- "fieldVaFormNumPages": 1,
- "fieldVaFormLinkTeasers": [
- {
- "entity": {
- "entityLabel": "About VA Form 21-0966 > Helpful links",
- "parentFieldName": "field_va_form_link_teasers",
- "entityId": "10997",
- "fieldLink": {
- "url": {
- "path": "/disability/how-to-file-claim"
- },
- "title": "How to file a VA disability claim",
- "options": []
- },
- "fieldLinkSummary": "Learn about the steps for filing a claim for disability compensation or increased disability compensation. Note: If you file your disability claim online, you don’t need to submit a paper Intent to File form. "
- }
- },
- {
- "entity": {
- "entityLabel": "About VA Form 21-0966 > Helpful links",
- "parentFieldName": "field_va_form_link_teasers",
- "entityId": "10998",
- "fieldLink": {
- "url": {
- "path": "/pension/how-to-apply"
- },
- "title": "How to apply for a VA pension as a Veteran",
- "options": []
- },
- "fieldLinkSummary": "Find out how to apply for tax-free VA pension benefits as a Veteran. Note: If you apply online for pension benefits, you still need to submit VA Form 21-0996 as your intent to file. "
- }
- }
- ],
- "fieldVaFormRelatedForms": [
- {
- "entity": {
- "fieldVaFormNumber": "10-10d"
- }
- }
- ],
- "changed": 1594906691
-}
diff --git a/modules/va_forms/spec/lib/regex_helper_spec.rb b/modules/va_forms/spec/lib/regex_helper_spec.rb
deleted file mode 100644
index 0cd10671141..00000000000
--- a/modules/va_forms/spec/lib/regex_helper_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: false
-
-require 'rails_helper'
-require 'va_forms/regex_helper'
-
-RSpec.describe VAForms::RegexHelper
-
-context 'When a form number is passed' do
- let(:helper) { VAForms::RegexHelper.new }
-
- it 'checked for VA and Form prefix and removed' do
- result = helper.scrub_query('VA Form 9')
- expect(result).to eq('9')
- end
-
- it 'checks for 1010 no dash and adds dash' do
- result = helper.scrub_query('1010')
- expect(result).to eq('10-10')
- end
-
- it 'checks for 10 10 no dash and adds dash' do
- result = helper.scrub_query('10 10')
- expect(result).to eq('10-10')
- end
-
- it 'checks for 10 10ez no dash and adds dash retain letters' do
- result = helper.scrub_query('10 10ez')
- expect(result).to eq('10-10ez')
- end
-end
diff --git a/modules/va_forms/spec/models/form_spec.rb b/modules/va_forms/spec/models/form_spec.rb
deleted file mode 100644
index bbfcf8563bf..00000000000
--- a/modules/va_forms/spec/models/form_spec.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe VAForms::Form, type: :model do
- describe 'callbacks' do
- it 'sets the last_revision to the first issued date if blank' do
- form = VAForms::Form.new
- form.form_name = '526ez'
- form.url = 'https://va.gov/va_form/21-526ez.pdf'
- form.title = 'Disability Compensation'
- form.first_issued_on = Time.zone.today - 1.day
- form.pages = 2
- form.sha256 = 'somelongsha'
- form.valid_pdf = true
- form.row_id = 4909
- form.save
- form.reload
- expect(form.last_revision_on).to eq(form.first_issued_on)
- end
-
- describe '#set_sha256_history' do
- let(:form) { create(:va_form) }
- let(:new_sha256) { '68b6d817881be1a1c8f323a9073a343b81d1c5a6e03067f27fe595db77645c22' }
-
- context 'when the sha256 has changed' do
- it 'updates the last_sha256_change and change_history' do
- form.sha256 = new_sha256
- form.save!
-
- current_date = Time.zone.today
- new_history = { 'sha256' => new_sha256, 'revision_on' => current_date.strftime('%Y-%m-%d') }
-
- expect(form.reload.last_sha256_change).to eq(current_date)
- expect(form.reload.change_history['versions']).to include(new_history)
- end
- end
-
- context 'when the sha256 has not changed' do
- it 'does not update the last_sha256_change or change_history' do
- last_sha256_change = form.last_sha256_change
- change_history = form.change_history
-
- form.title = 'A new title'
- form.save
-
- expect(form.reload.last_sha256_change).to eq(last_sha256_change)
- expect(form.reload.change_history).to eq(change_history)
- end
- end
- end
- end
-
- describe '.normalized_form_url' do
- context 'when the url starts with http' do
- let(:starting_url) { 'http://www.va.gov/vaforms/medical/pdf/vha10-10171-fill.pdf' }
- let(:ending_url) { 'https://www.va.gov/vaforms/medical/pdf/vha10-10171-fill.pdf' }
-
- it 'returns the url with http replaced with https' do
- expect(described_class.normalized_form_url(starting_url)).to eq(ending_url)
- end
- end
-
- context 'when the url does not start with http' do
- let(:starting_url) { './medical/pdf/vha10-10171-fill.pdf' }
- let(:ending_url) { 'https://www.va.gov/vaforms/medical/pdf/vha10-10171-fill.pdf' }
-
- it 'calls the expanded_va_url method' do
- expect(described_class).to receive(:expanded_va_url).with(starting_url).and_return(ending_url)
- described_class.normalized_form_url(starting_url)
- end
- end
-
- it 'returns the encoded url' do
- starting_url = 'https://www.va.gov/vaforms/medical/pdf/VHA 10-10171 (Fill).pdf'
- ending_url = 'https://www.va.gov/vaforms/medical/pdf/VHA%2010-10171%20(Fill).pdf'
-
- expect(described_class.normalized_form_url(starting_url)).to eq(ending_url)
- end
- end
-
- describe '.expanded_va_url' do
- context 'when the url starts with ./medical' do
- let(:starting_url) { './medical/pdf/vha10-10171-fill.pdf' }
- let(:ending_url) { 'https://www.va.gov/vaforms/medical/pdf/vha10-10171-fill.pdf' }
-
- it 'returns the expanded url' do
- expect(described_class.expanded_va_url(starting_url)).to eq(ending_url)
- end
- end
-
- context 'when the url starts with ./va' do
- let(:starting_url) { './va/pdf/10182-fill.pdf' }
- let(:ending_url) { 'https://www.va.gov/vaforms/va/pdf/10182-fill.pdf' }
-
- it 'returns the expanded url' do
- expect(described_class.expanded_va_url(starting_url)).to eq(ending_url)
- end
- end
-
- context 'when the url does not start with ./medical or ./va' do
- let(:starting_url) { './pdf/10182-fill.pdf' }
-
- it 'raises an ArgumentError' do
- expect { described_class.expanded_va_url(starting_url) }.to raise_error(ArgumentError)
- end
- end
- end
-end
diff --git a/modules/va_forms/spec/rails_helper.rb b/modules/va_forms/spec/rails_helper.rb
deleted file mode 100644
index 165ad751428..00000000000
--- a/modules/va_forms/spec/rails_helper.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require 'support/factory_bot'
diff --git a/modules/va_forms/spec/requests/metadata_spec.rb b/modules/va_forms/spec/requests/metadata_spec.rb
deleted file mode 100644
index ec4e9715c0a..00000000000
--- a/modules/va_forms/spec/requests/metadata_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require 'va_forms/health_checker'
-
-RSpec.describe 'VAForms::Metadata', type: :request do
- describe '#get /metadata' do
- it 'returns metadata JSON' do
- get '/services/va_forms/metadata'
- expect(response).to have_http_status(:ok)
- end
- end
-
- describe '#healthchecks' do
- def common_health_checks
- get '/services/va_forms/v0/healthcheck'
- parsed_response = JSON.parse(response.body)
- expect(response).to have_http_status(:ok)
- expect(parsed_response['description']).to eq(VAForms::HealthChecker::HEALTH_DESCRIPTION)
- expect(parsed_response['status']).to eq('UP')
- expect(parsed_response['time']).not_to be_nil
- end
-
- context 'v0' do
- it 'returns correct response and status when healthy' do
- allow(VAForms::Form).to receive(:count).and_return(1)
- common_health_checks
- end
-
- it 'returns UP status even when upstream is not healthy' do
- common_health_checks
- end
- end
- end
-
- describe '#upstream_healthcheck' do
- before do
- time = Time.utc(2020, 9, 21, 0, 0, 0)
- Timecop.freeze(time)
- end
-
- after { Timecop.return }
-
- def common_upstream_health_checks(path)
- get path
- parsed_response = JSON.parse(response.body)
- expect(parsed_response['description']).to eq(VAForms::HealthChecker::HEALTH_DESCRIPTION_UPSTREAM)
- expect(parsed_response['time']).to eq('2020-09-21T00:00:00Z')
-
- details = parsed_response['details']
- expect(details['name']).to eq('All upstream services')
-
- upstream_service = details['upstreamServices'].first
- expect(details['upstreamServices'].size).to eq(1)
- expect(upstream_service['description']).to eq(VAForms::HealthChecker::CMS_SERVICE)
- expect(upstream_service['details']['name']).to eq(VAForms::HealthChecker::CMS_SERVICE)
- expect(upstream_service['details']['time']).to eq('2020-09-21T00:00:00Z')
- { parsed_response:, upstream_service: }
- end
-
- def healthy_checks(path)
- allow(VAForms::Form).to receive(:count).and_return(1)
- results = common_upstream_health_checks(path)
- expect(response).to have_http_status(:ok)
- parsed_response = results[:parsed_response]
- upstream_service = results[:upstream_service]
- expect(parsed_response['status']).to eq('UP')
- expect(upstream_service['status']).to eq('UP')
- expect(upstream_service['details']['statusCode']).to eq(200)
- expect(upstream_service['details']['status']).to eq('OK')
- end
-
- def unhealthy_checks(path)
- results = common_upstream_health_checks(path)
- expect(response).to have_http_status(:service_unavailable)
- parsed_response = results[:parsed_response]
- upstream_service = results[:upstream_service]
- expect(parsed_response['status']).to eq('DOWN')
- expect(upstream_service['status']).to eq('DOWN')
- expect(upstream_service['details']['statusCode']).to eq(503)
- expect(upstream_service['details']['status']).to eq('Unavailable')
- end
-
- context 'v0' do
- path = '/services/va_forms/v0/upstream_healthcheck'
- it 'returns correct response and status when healthy', skip: 'No expectation in this example' do
- healthy_checks(path)
- end
-
- it 'returns correct status when cms is not healthy', skip: 'No expectation in this example' do
- unhealthy_checks(path)
- end
- end
- end
-end
diff --git a/modules/va_forms/spec/requests/va_forms/v0/apidocs_spec.rb b/modules/va_forms/spec/requests/va_forms/v0/apidocs_spec.rb
deleted file mode 100644
index c3317a89523..00000000000
--- a/modules/va_forms/spec/requests/va_forms/v0/apidocs_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'VAForms::V0::Apidocs', type: :request do
- describe '#get /docs/v1/api' do
- it 'returns Open API Spec v3 JSON' do
- get '/services/va_forms/docs/v0/api'
- expect(response).to have_http_status(:ok)
- JSON.parse(response.body)
- end
- end
-end
diff --git a/modules/va_forms/spec/requests/va_forms/v0/forms_spec.rb b/modules/va_forms/spec/requests/va_forms/v0/forms_spec.rb
deleted file mode 100644
index 4019f264fae..00000000000
--- a/modules/va_forms/spec/requests/va_forms/v0/forms_spec.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe 'VAForms::V0::Forms', type: :request do
- include SchemaMatchers
-
- let!(:form) do
- create(:va_form)
- create(:va_form, form_name: '527', row_id: '4157')
- create(:deleted_va_form)
- create(:va_form, form_name: '21-2001', row_id: '4158')
- end
- let(:base_url) { '/services/va_forms/v0/forms' }
- let(:inflection_header) { { 'X-Key-Inflection' => 'camel' } }
-
- describe 'GET :index' do
- it 'returns the forms, including those that were deleted' do
- get base_url
- data = JSON.parse(response.body)['data']
- expect(JSON.parse(response.body)['data'].length).to eq(4)
- expect(data[1]['attributes']['deleted_at']).to be_nil
- expect(data[2]['attributes']['deleted_at']).to be_truthy
- expect(response).to match_response_schema('va_forms/forms')
- end
-
- it 'returns the forms when camel-inflected' do
- get base_url, headers: inflection_header
- expect(response).to match_camelized_response_schema('va_forms/forms')
- end
-
- it 'correctly returns a matched query' do
- get "#{base_url}?query=526"
- expect(response).to match_response_schema('va_forms/forms')
- end
-
- it 'correctly returns a matched query when camel-inflected' do
- get "#{base_url}?query=526", headers: inflection_header
- expect(response).to match_camelized_response_schema('va_forms/forms')
- end
-
- it 'correctly returns a matched query while ignoring leading and trailing whitespace' do
- get "#{base_url}?query=%20526%20"
- expect(response).to match_response_schema('va_forms/forms')
- end
-
- it 'correctly returns a matched query while ignoring leading and trailing whitespace when camel-inflected' do
- get "#{base_url}?query=%20526%20", headers: inflection_header
- expect(response).to match_camelized_response_schema('va_forms/forms')
- end
-
- it 'correctly returns a matched query using keywords separated by whitespace' do
- get "#{base_url}?query=disability%20form"
- expect(response).to match_response_schema('va_forms/forms')
- end
-
- it 'correctly returns a matched query using keywords separated by whitespace when camel-inflected' do
- get "#{base_url}?query=disability%20form", headers: inflection_header
- expect(response).to match_camelized_response_schema('va_forms/forms')
- end
-
- it 'correctly searches on word root' do
- result = VAForms::Form.search('Disabilities')
- expect(result.first.title).to eq(form.title)
- end
-
- it 'returns all forms when asked' do
- expect(VAForms::Form.return_all.count).to eq(4)
- end
-
- it 'correctly passes the regex test for Form Number 21-XXXX' do
- expect(VAForms::Form).to receive(:search_by_form_number).with('21-2001')
- get "#{base_url}?query=21-2001"
- end
-
- it 'returns the date of the last sha256 change' do
- get "#{base_url}?query=527"
- last_sha256_change = JSON.parse(response.body)['data'][0]['attributes']['last_sha256_change']
- expect(last_sha256_change).to eql(form.last_sha256_change.strftime('%Y-%m-%d'))
- end
- end
-
- describe 'GET :show' do
- it 'returns the form' do
- get "#{base_url}/#{form.form_name}"
- expect(response).to match_response_schema('va_forms/form')
- end
-
- it 'has a created date' do
- get "#{base_url}/#{form.form_name}"
- data = JSON.parse(response.body)['data']
- expect(data['attributes']['created_at']).to be_truthy
- end
-
- it 'returns a 404 when a form is not there' do
- get "#{base_url}/bad"
- expect(response).to have_http_status(:not_found)
- end
-
- it 'returns the forms when camel-inflected' do
- get "#{base_url}/#{form.form_name}", headers: inflection_header
- expect(response).to match_camelized_response_schema('va_forms/form')
- end
-
- it 'returns the form version history' do
- get "#{base_url}/#{form.form_name}"
- versions = JSON.parse(response.body)['data']['attributes']['versions']
- expect(versions).to eql(form.change_history['versions'])
- end
- end
-end
diff --git a/modules/va_forms/spec/serializers/form_detail_serializer_spec.rb b/modules/va_forms/spec/serializers/form_detail_serializer_spec.rb
deleted file mode 100644
index 6bd4307777f..00000000000
--- a/modules/va_forms/spec/serializers/form_detail_serializer_spec.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe VAForms::FormDetailSerializer, type: :serializer do
- subject { serialize(va_form, serializer_class: described_class) }
-
- let(:va_form) { build_stubbed(:va_form, :has_been_deleted) }
- let(:data) { JSON.parse(subject)['data'] }
- let(:attributes) { data['attributes'] }
-
- it 'includes :id' do
- expect(data['id']).to eq va_form.row_id.to_s
- end
-
- it 'includes :form_name' do
- expect(attributes['form_name']).to eq va_form.form_name
- end
-
- it 'includes :url' do
- expect(attributes['url']).to eq va_form.url
- end
-
- it 'includes :title' do
- expect(attributes['title']).to eq va_form.title
- end
-
- it 'includes :first_issued_on' do
- expect(attributes['first_issued_on']).to eq va_form.first_issued_on.to_s
- end
-
- it 'includes :last_revision_on' do
- expect(attributes['last_revision_on']).to eq va_form.last_revision_on.to_s
- end
-
- it 'includes :created_at' do
- expect_time_eq(attributes['created_at'], va_form.created_at)
- end
-
- it 'includes :pages' do
- expect(attributes['pages']).to eq va_form.pages
- end
-
- it 'includes :sha256' do
- expect(attributes['sha256']).to eq va_form.sha256
- end
-
- it 'includes :valid_pdf' do
- expect(attributes['valid_pdf']).to eq va_form.valid_pdf
- end
-
- it 'includes :form_usage' do
- expect(attributes['form_usage']).to eq va_form.form_usage
- end
-
- it 'includes :form_tool_intro' do
- expect(attributes['form_tool_intro']).to eq va_form.form_tool_intro
- end
-
- it 'includes :form_tool_url' do
- expect(attributes['form_tool_url']).to eq va_form.form_tool_url
- end
-
- it 'includes :form_details_url' do
- expect(attributes['form_details_url']).to eq va_form.form_details_url
- end
-
- it 'includes :form_type' do
- expect(attributes['form_type']).to eq va_form.form_type
- end
-
- it 'includes :language' do
- expect(attributes['language']).to eq va_form.language
- end
-
- it 'includes :deleted_at' do
- expect_time_eq(attributes['deleted_at'], va_form.deleted_at)
- end
-
- it 'includes :related_forms' do
- expect(attributes['related_forms']).to eq va_form.related_forms
- end
-
- it 'includes :benefit_categories' do
- expect(attributes['benefit_categories']).to eq va_form.benefit_categories
- end
-
- it 'includes :va_form_administration' do
- expect(attributes['va_form_administration']).to eq va_form.va_form_administration
- end
-
- context 'when change_history exists' do
- it 'includes :versions' do
- expect(attributes['versions']).to eq va_form.change_history['versions']
- end
- end
-
- context 'when change_history is nil' do
- let(:va_form_no_history) { build_stubbed(:va_form, change_history: nil) }
- let(:response_no_history) { serialize(va_form_no_history, serializer_class: described_class) }
- let(:data_not_history) { JSON.parse(response_no_history)['data'] }
- let(:attributes_no_history) { data_not_history['attributes'] }
-
- it 'includes :versions as empty' do
- expect(attributes_no_history['versions']).to eq []
- end
- end
-end
diff --git a/modules/va_forms/spec/serializers/form_list_serializer_spec.rb b/modules/va_forms/spec/serializers/form_list_serializer_spec.rb
deleted file mode 100644
index d813157de25..00000000000
--- a/modules/va_forms/spec/serializers/form_list_serializer_spec.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe VAForms::FormListSerializer, type: :serializer do
- subject { serialize(va_form, serializer_class: described_class) }
-
- let(:va_form) { build_stubbed(:va_form, :has_been_deleted) }
- let(:data) { JSON.parse(subject)['data'] }
- let(:attributes) { data['attributes'] }
-
- it 'includes :id' do
- expect(data['id']).to eq va_form.row_id.to_s
- end
-
- it 'includes :form_name' do
- expect(attributes['form_name']).to eq va_form.form_name
- end
-
- it 'includes :url' do
- expect(attributes['url']).to eq va_form.url
- end
-
- it 'includes :title' do
- expect(attributes['title']).to eq va_form.title
- end
-
- it 'includes :first_issued_on' do
- expect(attributes['first_issued_on']).to eq va_form.first_issued_on.to_s
- end
-
- it 'includes :last_revision_on' do
- expect(attributes['last_revision_on']).to eq va_form.last_revision_on.to_s
- end
-
- it 'includes :pages' do
- expect(attributes['pages']).to eq va_form.pages
- end
-
- it 'includes :sha256' do
- expect(attributes['sha256']).to eq va_form.sha256
- end
-
- it 'includes :last_sha256_change' do
- expect(attributes['last_sha256_change']).to eq va_form.last_sha256_change
- end
-
- it 'includes :valid_pdf' do
- expect(attributes['valid_pdf']).to eq va_form.valid_pdf
- end
-
- it 'includes :form_usage' do
- expect(attributes['form_usage']).to eq va_form.form_usage
- end
-
- it 'includes :form_tool_intro' do
- expect(attributes['form_tool_intro']).to eq va_form.form_tool_intro
- end
-
- it 'includes :form_tool_url' do
- expect(attributes['form_tool_url']).to eq va_form.form_tool_url
- end
-
- it 'includes :form_details_url' do
- expect(attributes['form_details_url']).to eq va_form.form_details_url
- end
-
- it 'includes :form_type' do
- expect(attributes['form_type']).to eq va_form.form_type
- end
-
- it 'includes :language' do
- expect(attributes['language']).to eq va_form.language
- end
-
- it 'includes :deleted_at' do
- expect_time_eq(attributes['deleted_at'], va_form.deleted_at)
- end
-
- it 'includes :related_forms' do
- expect(attributes['related_forms']).to eq va_form.related_forms
- end
-
- it 'includes :benefit_categories' do
- expect(attributes['benefit_categories']).to eq va_form.benefit_categories
- end
-
- it 'includes :va_form_administration' do
- expect(attributes['va_form_administration']).to eq va_form.va_form_administration
- end
-end
diff --git a/modules/va_forms/spec/services/va_forms/slack/hash_notification_spec.rb b/modules/va_forms/spec/services/va_forms/slack/hash_notification_spec.rb
deleted file mode 100644
index a2b7e0ab8ff..00000000000
--- a/modules/va_forms/spec/services/va_forms/slack/hash_notification_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe VAForms::Slack::HashNotification do
- describe '#message_text' do
- let(:params) do
- {
- 'test_key' => 'test_value',
- 'args' => %w[1234 5678],
- 'gibberish' => 2,
- 'indeed' => 'indeed gibberish',
- 'message' => 'Something happened here'
- }
- end
-
- it 'returns the VSP environment' do
- with_settings(Settings, vsp_environment: 'staging') do
- expect(
- described_class.new(params).message_text
- ).to include('ENVIRONMENT: :construction: staging :construction:')
- end
- end
-
- it 'displays all the keys capitalized and formatted' do
- with_settings(Settings, vsp_environment: 'staging') do
- expect(described_class.new(params).message_text).to include(
- "\nTEST_KEY : test_value\nARGS : [\"1234\", \"5678\"]
-GIBBERISH : 2\nINDEED : indeed gibberish\nMESSAGE : Something happened here"
- )
- end
- end
- end
-end
diff --git a/modules/va_forms/spec/services/va_forms/slack/messenger_spec.rb b/modules/va_forms/spec/services/va_forms/slack/messenger_spec.rb
deleted file mode 100644
index 38d7e4a63a5..00000000000
--- a/modules/va_forms/spec/services/va_forms/slack/messenger_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe VAForms::Slack::Messenger do
- describe '.notify!' do
- let(:args) do
- {
- 'class' => 'SomeClass',
- 'args' => %w[1234 5678],
- 'error_class' => 'RuntimeError',
- 'error_message' => 'Here there be dragons'
- }
- end
- let(:api_key) { 'slack api key' }
- let(:channel_id) { 'slack channel id' }
-
- before { allow(Faraday).to receive(:post) }
-
- it 'makes a POST request to Slack with the message as the body' do
- with_settings(Settings.va_forms.slack, enabled: true, api_key:, channel_id:) do
- body = {
- text: VAForms::Slack::HashNotification.new(args).message_text,
- channel: channel_id
- }.to_json
-
- headers = {
- 'Content-type' => 'application/json; charset=utf-8',
- 'Authorization' => "Bearer #{api_key}"
- }
-
- expect(Faraday).to receive(:post).with(VAForms::Slack::Messenger::API_PATH, body, headers)
-
- VAForms::Slack::Messenger.new(args).notify!
- end
- end
-
- context 'when the Slack enabled setting is set to false' do
- it 'does not make a POST request to Slack' do
- with_settings(Settings.va_forms.slack, enabled: false) do
- expect(Faraday).not_to receive(:post)
-
- VAForms::Slack::Messenger.new(args).notify!
- end
- end
- end
- end
-end
diff --git a/modules/va_forms/spec/sidekiq/flipper_status_alert_spec.rb b/modules/va_forms/spec/sidekiq/flipper_status_alert_spec.rb
deleted file mode 100644
index ba3a7da1e95..00000000000
--- a/modules/va_forms/spec/sidekiq/flipper_status_alert_spec.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require 'flipper/utilities/bulk_feature_checker'
-
-describe VAForms::FlipperStatusAlert, type: :job do
- include FixtureHelpers
-
- before { Sidekiq::Job.clear_all }
-
- describe '#perform' do
- let(:messenger_instance) { instance_double(VAForms::Slack::Messenger) }
- let(:config_file_path) { VAForms::Engine.root.join('config', 'flipper', 'enabled_features.yml') }
-
- let(:warning_emoji) { described_class::WARNING_EMOJI }
- let(:traffic_light_emoji) { described_class::TRAFFIC_LIGHT_EMOJI }
-
- let(:missing_file_message) { "#{warning_emoji} #{described_class} features file does not exist." }
- let(:flag_message) { "#{warning_emoji} One or more features expected to be enabled were found to be disabled." }
-
- before do
- allow(VAForms::Slack::Messenger).to receive(:new).and_return(messenger_instance)
- allow(messenger_instance).to receive(:notify!)
- end
-
- it 'notifies Slack of missing config file when no config file found' do
- allow(File).to receive(:exist?).and_return(false)
- expected_notify = { warning: missing_file_message, file_path: config_file_path.to_s }
- expect(VAForms::Slack::Messenger).to receive(:new).with(expected_notify).and_return(messenger_instance)
- expect(messenger_instance).to receive(:notify!).once
-
- described_class.new.perform
- end
-
- it 'fetches enabled status of common and current env features when config file contains both' do
- allow(YAML).to receive(:load_file).and_return({ 'common' => %w[feature1], 'production' => %w[feature2] })
- bulk_checker_result = { enabled: [], disabled: %w[feature1 feature2] }
- expect(Flipper::Utilities::BulkFeatureChecker)
- .to receive(:enabled_status).with(%w[feature1 feature2]).and_return(bulk_checker_result)
-
- with_settings(Settings, vsp_environment: 'production') do
- described_class.new.perform
- end
- end
-
- it 'fetches enabled status of common features only when config file contains no current env features' do
- allow(YAML).to receive(:load_file).and_return({ 'common' => %w[feature1], 'development' => nil })
- bulk_checker_result = { enabled: [], disabled: %w[feature1] }
- expect(Flipper::Utilities::BulkFeatureChecker)
- .to receive(:enabled_status).with(%w[feature1]).and_return(bulk_checker_result)
-
- with_settings(Settings, vsp_environment: 'development') do
- described_class.new.perform
- end
- end
-
- it 'fetches enabled status of current env features only when config file contains no common features' do
- allow(YAML).to receive(:load_file).and_return({ 'common' => nil, 'staging' => %w[feature1] })
- bulk_checker_result = { enabled: [], disabled: %w[feature1] }
- expect(Flipper::Utilities::BulkFeatureChecker)
- .to receive(:enabled_status).with(%w[feature1]).and_return(bulk_checker_result)
-
- with_settings(Settings, vsp_environment: 'staging') do
- described_class.new.perform
- end
- end
-
- it 'does not notify Slack when all features are enabled' do
- allow(YAML).to receive(:load_file).and_return({ 'common' => %w[feature1 feature2] })
- bulk_checker_result = { enabled: %w[feature1 feature2], disabled: [] }
- allow(Flipper::Utilities::BulkFeatureChecker).to receive(:enabled_status).and_return(bulk_checker_result)
- expect(VAForms::Slack::Messenger).not_to receive(:new)
-
- described_class.new.perform
- end
-
- it 'notifies Slack when some features are disabled' do
- allow(YAML).to receive(:load_file).and_return({ 'common' => %w[feature1 feature2 feature3] })
- bulk_checker_result = { enabled: %w[feature1], disabled: %w[feature2 feature3] }
- allow(Flipper::Utilities::BulkFeatureChecker).to receive(:enabled_status).and_return(bulk_checker_result)
- expected_notify = {
- class: described_class.name,
- warning: flag_message,
- disabled_flags: "#{traffic_light_emoji} feature2, feature3 #{traffic_light_emoji}"
- }
- expect(VAForms::Slack::Messenger).to receive(:new).with(expected_notify).and_return(messenger_instance)
- expect(messenger_instance).to receive(:notify!).once
-
- described_class.new.perform
- end
- end
-end
diff --git a/modules/va_forms/spec/sidekiq/form_builder_spec.rb b/modules/va_forms/spec/sidekiq/form_builder_spec.rb
deleted file mode 100644
index 4ee13faa26d..00000000000
--- a/modules/va_forms/spec/sidekiq/form_builder_spec.rb
+++ /dev/null
@@ -1,319 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require VAForms::Engine.root.join('spec', 'rails_helper.rb')
-
-# rubocop:disable Layout/LineLength
-RSpec.describe VAForms::FormBuilder, type: :job do
- subject { described_class }
-
- let(:form_builder) { described_class.new }
- let(:slack_messenger) { instance_double(VAForms::Slack::Messenger) }
-
- let(:default_form_data) { JSON.parse(File.read(VAForms::Engine.root.join('spec', 'fixtures', 'gql_form.json'))) }
- let(:invalid_url_form_data) { JSON.parse(File.read(VAForms::Engine.root.join('spec', 'fixtures', 'gql_form_invalid_url.json'))) }
- let(:deleted_form_data) { JSON.parse(File.read(VAForms::Engine.root.join('spec', 'fixtures', 'gql_form_deleted.json'))) }
-
- let(:valid_pdf_cassette) { 'va_forms/valid_pdf' }
- let(:not_found_pdf_cassette) { 'va_forms/pdf_not_found' }
- let(:server_error_pdf_cassette) { 'va_forms/pdf_internal_server_error' }
-
- let(:form_fetch_error_message) { 'The form could not be fetched from the url provided. Response code: 500' }
-
- before do
- Sidekiq::Job.clear_all
- allow(Rails.logger).to receive(:error)
- allow(VAForms::Slack::Messenger).to receive(:new).and_return(slack_messenger)
- allow(slack_messenger).to receive(:notify!)
- allow(StatsD).to receive(:increment)
- end
-
- describe '#perform' do
- let(:form_name) { '21-0966' }
- let(:url) { 'https://www.vba.va.gov/pubs/forms/VBA-21-0966-ARE.pdf' }
- let(:valid_sha256) { 'b1ee32f44d7c17871e4aba19101ba6d55742674e6e1627498d618a356ea6bc78' }
- let(:sha256) { valid_sha256 }
- let(:title) { form_data['fieldVaFormName'] }
- let(:row_id) { form_data['fieldVaFormRowId'] }
- let(:valid_pdf) { true }
- let(:form_data) { default_form_data }
- let(:enable_notifications) { true }
- let(:result) do
- form = VAForms::Form.create!(url:, form_name:, sha256:, title:, valid_pdf:, row_id:)
- with_settings(Settings.va_forms.slack, enabled: enable_notifications) do
- VCR.use_cassette(valid_pdf_cassette) do
- form_builder.perform(form_data)
- end
- end
- form.reload
- end
-
- context 'when the form url returns a successful response' do
- it 'correctly updates attributes based on the new form data' do
- expect(result).to have_attributes(
- form_name: '21-0966',
- row_id: 5382,
- url: 'https://www.vba.va.gov/pubs/forms/VBA-21-0966-ARE.pdf',
- title: 'Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC',
- first_issued_on: Date.new(2019, 11, 7),
- last_revision_on: Date.new(2018, 8, 22),
- pages: 1,
- sha256: 'b1ee32f44d7c17871e4aba19101ba6d55742674e6e1627498d618a356ea6bc78',
- valid_pdf: true,
- ranking: nil,
- tags: '21-0966',
- language: 'en',
- related_forms: ['10-10d'],
- benefit_categories: [{ 'name' => 'Pension', 'description' => 'VA pension benefits' }],
- va_form_administration: 'Veterans Benefits Administration',
- form_type: 'benefit',
- form_usage: 'Someusagehtml',
- form_tool_intro: 'some intro text',
- form_tool_url: 'https://www.va.gov/education/apply-for-education-benefits/application/1995/introduction',
- form_details_url: 'https://www.va.gov/find-forms/about-form-21-0966',
- deleted_at: nil
- )
- end
- end
-
- context 'when the form url returns a 404' do
- let(:form_data) { invalid_url_form_data }
- let(:invalid_form_url) { 'https://www.vba.va.gov/pubs/forms/not_a_valid_url.pdf' }
- let(:result) do
- form = VAForms::Form.create!(url:, form_name:, sha256:, title:, valid_pdf:, row_id:)
- with_settings(Settings.va_forms.slack, enabled: enable_notifications) do
- VCR.use_cassette(not_found_pdf_cassette) do
- form_builder.perform(form_data)
- end
- end
- form.reload
- end
-
- it 'marks the form as invalid' do
- expect(result.valid_pdf).to be(false)
- end
-
- it 'updates the form url' do
- expect(result.url).to eql(invalid_form_url)
- end
-
- it 'clears the sha256' do
- expect(result.sha256).to be_nil
- end
-
- it 'correctly updates the remaining attributes based on the form data' do
- expect(result).to have_attributes(
- form_name: '21-0966',
- row_id: 5382,
- title: 'Intent to File a Claim for Compensation and/or Pension, or Survivors Pension and/or DIC',
- first_issued_on: Date.new(2019, 11, 7),
- last_revision_on: Date.new(2018, 8, 22),
- pages: 1,
- ranking: nil,
- tags: '21-0966',
- language: 'en',
- related_forms: ['10-10d'],
- benefit_categories: [{ 'name' => 'Pension', 'description' => 'VA pension benefits' }],
- va_form_administration: 'Veterans Benefits Administration',
- form_type: 'benefit',
- form_usage: 'Someusagehtml',
- form_tool_intro: 'some intro text',
- form_tool_url: 'https://www.va.gov/education/apply-for-education-benefits/application/1995/introduction',
- form_details_url: 'https://www.va.gov/find-forms/about-form-21-0966',
- deleted_at: nil
- )
- end
-
- it 'notifies slack that the form url no longer returns a valid form' do
- result
- expect(VAForms::Slack::Messenger).to have_received(:new).with(
- {
- class: described_class.to_s,
- message: "URL for form #{form_name} no longer returns a valid PDF or web page.",
- form_url: invalid_form_url
- }
- )
- expect(slack_messenger).to have_received(:notify!)
- end
- end
-
- context 'when the form url returns a 500' do
- it 'raises an error' do
- VCR.use_cassette(server_error_pdf_cassette) do
- expect { form_builder.perform(form_data) }
- .to raise_error(described_class::FormFetchError, form_fetch_error_message)
- end
- end
- end
-
- context 'when the PDF is unchanged' do
- it 'keeps existing values without notifying slack' do
- expect(result.valid_pdf).to be(true)
- expect(result.sha256).to eq(valid_sha256)
- expect(slack_messenger).not_to have_received(:notify!)
- end
- end
-
- context 'when the PDF has been marked as deleted' do
- let(:form_data) { deleted_form_data }
-
- it 'updates the deleted_at date' do
- expect(result.deleted_at.to_date.to_s).to eq('2020-07-16')
- end
-
- it 'sets valid_pdf to true and the sha256 to nil' do
- expect(result.valid_pdf).to be(false)
- expect(result.sha256).to be_nil
- end
-
- it 'does not raise a form fetch error' do
- expect { form_builder.perform(form_data) }
- .not_to raise_error
- end
- end
-
- context 'when the PDF was previously invalid' do
- let(:valid_pdf) { false }
-
- it 'updates valid_pdf to true without notifying slack' do
- expect(result.valid_pdf).to be(true)
- expect(slack_messenger).not_to have_received(:notify!)
- end
- end
-
- context 'when the sha256 has changed' do
- let(:sha256) { 'arbitrary-old-sha256-value' }
-
- context 'and the url returns a PDF' do
- it 'updates the saved sha256 and notifies slack' do
- expect(result.sha256).to eq(valid_sha256)
- expect(VAForms::Slack::Messenger).to have_received(:new).with(
- {
- class: described_class.to_s,
- message: "PDF contents of form #{form_name} have been updated."
- }
- )
- expect(slack_messenger).to have_received(:notify!)
- end
- end
-
- context 'and the url returns a web page' do
- before do
- allow_any_instance_of(Faraday::Utils::Headers).to receive(:[]).with(:user_agent).and_call_original
- allow_any_instance_of(Faraday::Utils::Headers).to receive(:[]).with('Content-Type').and_return('text/html')
- end
-
- it 'updates the saved sha256 but does not notify slack' do
- expect(result.sha256).to eq(valid_sha256)
- expect(slack_messenger).not_to have_received(:notify!)
- end
- end
- end
-
- context 'when all retries are exhausted' 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!',
- 'args' => [{
- 'fieldVaFormNumber' => form_name,
- 'fieldVaFormRowId' => row_id
- }]
- }
- end
-
- it 'increments the StatsD counter' do
- described_class.within_sidekiq_retries_exhausted_block(msg, error) do
- expect(StatsD).to(receive(:increment))
- .with("#{described_class::STATSD_KEY_PREFIX}.exhausted", tags: { form_name:, row_id: })
- .exactly(1).time
- end
- end
-
- it 'logs a warning to the Rails console' do
- described_class.within_sidekiq_retries_exhausted_block(msg, error) do
- expect(Rails.logger).to receive(:warn).with(
- 'VAForms::FormBuilder retries exhausted',
- {
- job_id: 123,
- error_class: 'RuntimeError',
- error_message: 'an error occurred!',
- form_name:,
- row_id:,
- form_data: {
- 'fieldVaFormNumber' => form_name,
- 'fieldVaFormRowId' => row_id
- }
- }
- )
- end
- end
-
- context 'and the error was a form fetch error' do
- let(:error) { described_class::FormFetchError.new(form_fetch_error_message) }
- let(:msg) do
- {
- 'jid' => 456,
- 'error_class' => described_class::FormFetchError.to_s,
- 'error_message' => form_fetch_error_message,
- 'args' => [{
- 'fieldVaFormNumber' => form_name,
- 'fieldVaFormRowId' => row_id,
- 'fieldVaFormUrl' => {
- 'uri' => url
- }
- }]
- }
- end
-
- it 'updates the url-related form attributes' do
- form = VAForms::Form.create!(url:, form_name:, sha256:, title:, valid_pdf:, row_id:)
-
- described_class.within_sidekiq_retries_exhausted_block(msg, error) do
- expect(StatsD).to(receive(:increment)).exactly(1).time
- expect(Rails.logger).to receive(:warn)
- end
-
- form.reload
- expect(form.valid_pdf).to be(false)
- expect(form.sha256).to be_nil
- expect(form.url).to eq(url)
- end
-
- context 'and the form was previously valid' do
- let(:expected_notify) do
- {
- class: described_class.to_s,
- message: "URL for form #{form_name} no longer returns a valid PDF or web page.",
- form_url: url
- }
- end
-
- it 'notifies Slack that the form is now invalid' do
- VAForms::Form.create!(url:, form_name:, sha256:, title:, valid_pdf: true, row_id:)
-
- described_class.within_sidekiq_retries_exhausted_block(msg, error) do
- expect(VAForms::Slack::Messenger).to receive(:new).with(expected_notify).and_return(slack_messenger)
- expect(slack_messenger).to receive(:notify!)
- end
- end
- end
-
- context 'and the form was not previously valid' do
- it 'does not notify Slack' do
- VAForms::Form.create!(url:, form_name:, sha256:, title:, valid_pdf: false, row_id:)
-
- described_class.within_sidekiq_retries_exhausted_block(msg, error) do
- expect(VAForms::Slack::Messenger).not_to receive(:new)
- expect(slack_messenger).not_to receive(:notify!)
- end
- end
- end
- end
- end
- end
-end
-# rubocop:enable Layout/LineLength
diff --git a/modules/va_forms/spec/sidekiq/form_reloader_spec.rb b/modules/va_forms/spec/sidekiq/form_reloader_spec.rb
deleted file mode 100644
index d51e62dd51c..00000000000
--- a/modules/va_forms/spec/sidekiq/form_reloader_spec.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-require VAForms::Engine.root.join('spec', 'rails_helper.rb')
-
-RSpec.describe VAForms::FormReloader, type: :job do
- describe '#perform' do
- subject { described_class }
-
- let(:slack_messenger) { instance_double(VAForms::Slack::Messenger) }
- let(:form_count) { 1 } # gql_forms.yml cassette only returns one form
-
- before do
- Sidekiq::Job.clear_all
- allow(Rails.logger).to receive(:error)
- allow(VAForms::Slack::Messenger).to receive(:new).and_return(slack_messenger)
- allow(slack_messenger).to receive(:notify!)
- allow(StatsD).to receive(:increment)
- end
-
- it 'schedules a child FormBuilder job for each form retrieved' do
- with_settings(Settings.va_forms.form_reloader, enabled: true) do
- VCR.use_cassette('va_forms/forms') do
- described_class.new.perform
- expect(VAForms::FormBuilder.jobs.size).to eq(form_count)
- end
- end
- end
-
- context 'when the forms server returns an error' do
- it 'raises an error and does not schedule any child FormBuilder jobs' do
- with_settings(Settings.va_forms.form_reloader, enabled: true) do
- VCR.use_cassette('va_forms/forms_500_error') do
- expect { described_class.new.perform }.to raise_error(NoMethodError)
- expect(VAForms::FormBuilder.jobs.size).to eq(0)
- end
- end
- end
- end
-
- context 'when the job is disabled in settings' do
- it 'does not schedule any child FormBuilder jobs' do
- with_settings(Settings.va_forms.form_reloader, enabled: false) do
- VCR.use_cassette('va_forms/forms_500_error') do
- expect(VAForms::FormBuilder.jobs.size).to eq(0)
- end
- end
- end
- end
-
- context 'when all retries have been exhausted' 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 'increments the StatsD counter' do
- described_class.within_sidekiq_retries_exhausted_block(msg, error) do
- expect(StatsD).to(receive(:increment))
- .with("#{described_class::STATSD_KEY_PREFIX}.exhausted")
- .exactly(1).time
- end
- end
-
- it 'logs an error to the Rails console' do
- described_class.within_sidekiq_retries_exhausted_block(msg, error) do
- expect(Rails.logger).to receive(:error).with(
- 'VAForms::FormReloader retries exhausted',
- {
- job_id: 123,
- error_class: 'RuntimeError',
- error_message: 'an error occurred!'
- }
- )
- end
- end
-
- it 'notifies Slack' do
- described_class.within_sidekiq_retries_exhausted_block(msg, error) do
- expect(VAForms::Slack::Messenger).to receive(:new).with(
- {
- class: 'VAForms::FormReloader',
- exception: 'RuntimeError',
- exception_message: 'an error occurred!',
- detail: 'VAForms::FormReloader retries exhausted'
- }
- ).and_return(slack_messenger)
- expect(slack_messenger).to receive(:notify!)
- end
- end
- end
- end
-end
diff --git a/modules/va_forms/spec/spec_helper.rb b/modules/va_forms/spec/spec_helper.rb
deleted file mode 100644
index fffe861dfd6..00000000000
--- a/modules/va_forms/spec/spec_helper.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-# Configure Rails Envinronment
-ENV['RAILS_ENV'] = 'test'
-
-require 'rspec/rails'
-
-RSpec.configure do |config|
- config.use_transactional_fixtures = true
-end
diff --git a/modules/va_forms/va_forms.gemspec b/modules/va_forms/va_forms.gemspec
deleted file mode 100644
index 27f4c256fa5..00000000000
--- a/modules/va_forms/va_forms.gemspec
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-$LOAD_PATH.push File.expand_path('../lib', __FILE__)
-
-# Maintain your gem's version:
-require 'va_forms/version'
-
-# Describe your gem and declare its dependencies:
-Gem::Specification.new do |s|
- s.name = 'va_forms'
- s.version = VAForms::VERSION
- s.authors = ['Charley Stran']
- s.email = ['charley.stran@oddball.io']
- s.homepage = 'https://api.va.gov/services/va_forms/docs/v0'
- s.summary = 'VA Forms API'
- s.description = 'VA Forms API'
- s.license = 'CC0'
-
- s.files = Dir['{app,config,db,lib}/**/*', 'Rakefile']
- s.test_files = Dir['spec/**/*']
-
- s.add_dependency 'faraday'
- s.add_dependency 'nokogiri'
- s.add_dependency 'sidekiq'
-
- s.add_development_dependency 'factory_bot_rails'
- s.add_development_dependency 'pg'
- s.add_development_dependency 'rspec-rails'
-end
diff --git a/modules/va_notify/README.md b/modules/va_notify/README.md
index 14f2271d1a2..b2565a540b3 100644
--- a/modules/va_notify/README.md
+++ b/modules/va_notify/README.md
@@ -9,6 +9,7 @@ Depending on which business line you fall under, you may need to have a new Serv
There are several options for interacting with the `VaNotify` module
### Using the service class directly (inline/synchronous sending)
+
Example usage to send an email using the `VaNotify::Service` class (using va.gov's api key and template):
```ruby
@@ -59,7 +60,6 @@ This class defaults to using the va.gov service's api key but you can provide yo
)
```
-
### API key details
Api keys need to be structured using the following format:
@@ -70,6 +70,7 @@ Api keys need to be structured using the following format:
- `API_KEY` - Actual API key
Example for a service with the following attributes:
+
- Name of Api key: `foo-bar-normal-key`
- Service id: `aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa`
- Api key: `bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb`
@@ -79,23 +80,26 @@ Expected format: `foo-bar-normal-key-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa-bbbbbb
Please reach out via [#va-notify-public](https://dsva.slack.com/archives/C010R6AUPHT) if you have any questions.
#### Misc
+
ICNs are considered PII and therefore should not be logged or stored. https://depo-platform-documentation.scrollhelp.site/developer-docs/personal-identifiable-information-pii-guidelines#PersonalIdentifiableInformation(PII)guidelines-NotesandpoliciesregardingICNs
-# Zero Silent Failures Initiative.
-Providing some additional context around using VA Notify in `vets-api` and preventing silent failures for notifications.
+# Zero Silent Failures Initiative
-## VA Notify Error Classifications.
-### API Requests - System Availability, Request Authorization, and Data Validation.
+Providing some additional context around using VA Notify in `vets-api` and preventing silent failures for notifications
+
+## VA Notify Error Classifications
+
+### API Requests - System Availability, Request Authorization, and Data Validation
When a client makes an API call to VA Notify, the API first authorizes the request, and then confirms all required fields are present and in the appropriate format. Once this has been validated, the API will return a success code and notification_id, ending the transaction. You should save that notification_id for troubleshooting, and future status updates. From there, the notification proceeds to a delivery workflow.
-### Notification Delivery - Contact Lookups and Deliverability.
+### Notification Delivery - Contact Lookups and Deliverability
Our delivery workflow includes retries for errors that may be temporary in nature, like service availability. If your API request includes a recipient_identifier, then VA Notify kicks off our lookup integrations. First, we use MPI to do a deceased check and identify the correlated VA Profile ID. Once we have the VA Profile ID, we use VA Profile to retrieve the email address on file for the Veteran. If there are issues finding the Veteran’s profile or contact information, then VA Notify is unable to deliver the notification. This would indicate that the Veteran needs an alternative communication method or an updated email address. If an email address is successfully retrieved or the API request includes the email address directly, then the notification moves on to delivery via our email provider.
There are a couple of reasons that can cause an email notification to fail such as hard bounces and soft bounces. Hard bounces indicate a permanent failure due to an invalid, unreachable email address. Soft bounces indicate a temporary failure, which could succeed after retry. However, there’s many reasons for soft bounces, some of which require manual effort by the recipient or recipient’s organization if they are utilizing a managed email service (e.g. a work email). Email settings could be blocking these notifications from being delivered. If your notification continues to soft bounce, it’s unlikely to succeed with more send attempts.
-## API Requests - VA system to system communication.
+## API Requests - VA system to system communication.
### VA Notify provides a Rails module that exposes two ways of integrating.
@@ -103,11 +107,13 @@ There are a couple of reasons that can cause an email notification to fail such
2. Prebuilt sidekiq jobs eg `VANotify::EmailJob.perform_async(some_args)` basic example [here](https://github.com/department-of-veterans-affairs/vets-api/tree/master/modules/va_notify#using-the-wrapper-sidekiq-class-async-sending).
Using option #1:
+
- The VA Notify service class operates synchronously and will raise an exception whenever a request to the VA Notify API fails.
- - If you are using the service class to process the user's request inline (like a form submission) the exception will propagate up through the application (unless you have error handling that catches the failure) and cause the entire request to fail (which will then show the user an error message).
- - If you are using the service class within your own sidekiq job a VA Notify error will cause your sidekiq job to retry (unless you have error handling that catches the failure). You will need to have your own error handling in place to handle this scenario.
+ - If you are using the service class to process the user's request inline (like a form submission) the exception will propagate up through the application (unless you have error handling that catches the failure) and cause the entire request to fail (which will then show the user an error message).
+ - If you are using the service class within your own sidekiq job a VA Notify error will cause your sidekiq job to retry (unless you have error handling that catches the failure). You will need to have your own error handling in place to handle this scenario.
Using option #2:
+
- Invoking the sidekiq job via `.perform_async` - because this is an async call it will not fail inline.
- The sidekiq job could fail when it is picked by a sidekiq worker - if the job fails for any reason it will automatically [retry](https://github.com/department-of-veterans-affairs/vets-api/blob/master/modules/va_notify/app/sidekiq/va_notify/email_job.rb#L7) If the job continues to fail it will eventually go to the dead queue (visible in the [sidekiq dashboard](https://api.va.gov/sidekiq/morgue) and this Datadog [dashboard](https://app.ddog-gov.com/sb/f327ad72-c02a-11ec-a50a-da7ad0900007-260dfe9b82780fef7f07b002e4355281)).
@@ -115,7 +121,12 @@ Using option #2:
### VA Notify Callback Integration Guide for Vets-API
-To effectively track the status of individual notifications, VA Notify provides service callbacks. These callbacks enable you to determine whether a notification was successfully delivered or failed, allowing you to take appropriate action. This guide outlines two distinct approaches to integrating callback logic: Custom Callback Handler and Default Callback Class.
+To effectively track the status of individual notifications, VA Notify provides service callbacks. These callbacks enable you to determine whether a notification was successfully delivered or failed, allowing you to take appropriate action.
+
+This guide outlines two distinct approaches to integrating callback logic:
+
+- [Default Callback Class][1]
+- [Custom Callback Handler][2]
#### Why Teams Need to Integrate with Callback Logic
@@ -134,6 +145,7 @@ A successful request to the VA Notify API does not guarantee that the recipient
#### How Teams Can Integrate with Callbacks
**Option 1: Default Callback Class**
+
The Default Callback Class offers a standard, ready-to-use implementation for handling callbacks.
@@ -141,23 +153,40 @@ Example Implementation
Step 1: Set Up the Notification Trigger
-```
+```rb
+# VANotify::EmailJob or VANotify::UserAccountJob
+
VANotify::EmailJob.perform_async(
user.va_profile_email,
template_id,
get_personalization(first_name),
Settings.vanotify.services.va_gov.api_key,
- {
- callback_metadata: {
- notification_type: 'error',
- form_number: 'ExampleForm1234',
+ {
+ callback_metadata: {
+ notification_type: 'error',
+ form_number: 'ExampleForm1234',
statsd_tags: { service: 'DefaultService', function: 'DefaultFunction' }
- }
+ }
}
)
+
+# VANotify::Service
+
+callback_options = {
+ callback_metadata: {
+ notification_type: 'error',
+ form_number: 'ExampleForm1234',
+ statsd_tags: { service: 'DefaultService', function: 'DefaultFunction' }
+ }
+}
+
+notify_client = VaNotify::Service.new(api_key, callback_options)
+
+notify_response = notify_client.send_email(....)
```
**Option 2: Custom Callback Handler**
+
The Custom Callback Handler allows teams to create a bespoke solution tailored to their specific requirements. This approach offers complete control over how delivery statuses are processed and logged.
@@ -165,7 +194,7 @@ Example Implementation
Step 1: Create a Callback Handler Class: Define a class in your module to handle callbacks, which must implement a class-level method `.call`.
-```
+```rb
module ExampleTeam
class CustomNotificationCallback
def self.call(notification)
@@ -197,7 +226,10 @@ end
Step 2: Integrate Callback Logic in Notification Triggers: Behind a feature flag, choose one of your notification triggers and update the way you are invoking VA Notify to pass in your callback data.
Here is an example:
-```
+
+```rb
+# VANotify::EmailJob or VANotify::UserAccountJob
+
if Flipper.enabled?(:custom_callback_handler)
VANotify::EmailJob.perform_async(
user.va_profile_email,
@@ -210,6 +242,7 @@ else
# Default logic
end
```
+
---
#### Behind the Scenes: How Callbacks Work
@@ -239,3 +272,6 @@ Refer to the [VA Notify Error Status Mapping Table](https://github.com/departmen
If you need any further clarification or help during the integration process, feel free to reach out:
- Slack Channel: [#va-notify-public](https://dsva.slack.com/archives/C010R6AUPHT)
+
+[1]: #default-callback
+[2]: #custom-callback
diff --git a/modules/va_notify/app/controllers/va_notify/callbacks_controller.rb b/modules/va_notify/app/controllers/va_notify/callbacks_controller.rb
index 4629ba05363..66802f61510 100644
--- a/modules/va_notify/app/controllers/va_notify/callbacks_controller.rb
+++ b/modules/va_notify/app/controllers/va_notify/callbacks_controller.rb
@@ -17,8 +17,15 @@ def create
notification_id = params[:id]
if (notification = VANotify::Notification.find_by(notification_id: notification_id))
- Rails.logger.info("va_notify callbacks - Updating notification #{notification.id}")
notification.update(notification_params)
+ Rails.logger.info("va_notify callbacks - Updating notification: #{notification.id}",
+ {
+ source_location: notification.source_location,
+ template_id: notification.template_id,
+ callback_metadata: notification.callback_metadata,
+ status: notification.status
+ })
+
VANotify::DefaultCallback.new(notification).call
VANotify::StatusUpdate.new.delegate(notification_params.merge(id: notification_id))
else
diff --git a/modules/va_notify/app/models/va_notify/notification.rb b/modules/va_notify/app/models/va_notify/notification.rb
index 67201095372..e82a12bca7c 100644
--- a/modules/va_notify/app/models/va_notify/notification.rb
+++ b/modules/va_notify/app/models/va_notify/notification.rb
@@ -3,5 +3,8 @@
module VANotify
class Notification < ApplicationRecord
self.table_name = 'va_notify_notifications'
+
+ has_kms_key
+ has_encrypted :to, migrating: true, key: :kms_key, **lockbox_options
end
end
diff --git a/modules/va_notify/lib/va_notify/service.rb b/modules/va_notify/lib/va_notify/service.rb
index 29a45426f57..3125072c006 100644
--- a/modules/va_notify/lib/va_notify/service.rb
+++ b/modules/va_notify/lib/va_notify/service.rb
@@ -164,7 +164,7 @@ def find_caller_locations
caller_locations.each do |location|
next if ignored_files.any? { |path| location.path.include?(path) }
- return "#{location.path}:#{location.lineno} in #{location.label}"
+ return "#{location.path}:#{location.lineno} in #{location.base_label}"
end
end
end
diff --git a/modules/va_notify/spec/requests/callbacks_spec.rb b/modules/va_notify/spec/requests/callbacks_spec.rb
index 57f651443a7..9a46b2caa86 100644
--- a/modules/va_notify/spec/requests/callbacks_spec.rb
+++ b/modules/va_notify/spec/requests/callbacks_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'rails_helper'
+require 'va_notify/default_callback'
RSpec.describe 'VANotify Callbacks', type: :request do
let(:valid_token) { Settings.vanotify.status_callback.bearer_token }
@@ -18,13 +19,26 @@
describe 'POST #notifications' do
it 'with found notification' do
- notification = VANotify::Notification.create(notification_id: notification_id)
+ template_id = SecureRandom.uuid
+ notification = VANotify::Notification.create(notification_id: notification_id,
+ source_location: 'some_location',
+ callback_metadata: 'some_callback_metadata',
+ template_id: template_id)
expect(notification.status).to eq(nil)
+ allow(Rails.logger).to receive(:info)
+ callback_obj = double('VANotify::DefaultCallback')
+ allow(VANotify::DefaultCallback).to receive(:new).and_return(callback_obj)
+ allow(callback_obj).to receive(:call)
post(callback_route,
params: callback_params.to_json,
headers: { 'Authorization' => "Bearer #{valid_token}", 'Content-Type' => 'application/json' })
+ expect(Rails.logger).to have_received(:info).with(
+ "va_notify callbacks - Updating notification: #{notification.id}",
+ { source_location: 'some_location', template_id: template_id, callback_metadata: 'some_callback_metadata',
+ status: 'delivered' }
+ )
expect(response.body).to include('success')
notification.reload
expect(notification.status).to eq('delivered')
diff --git a/modules/vaos/app/controllers/vaos/v2/clinics_controller.rb b/modules/vaos/app/controllers/vaos/v2/clinics_controller.rb
index e95d95f534d..9efbc5e2f0c 100644
--- a/modules/vaos/app/controllers/vaos/v2/clinics_controller.rb
+++ b/modules/vaos/app/controllers/vaos/v2/clinics_controller.rb
@@ -42,29 +42,35 @@ def last_visited_clinic
end
def recent_facilities
- sorted_facilities = []
sorted_appointments = appointments_service.get_recent_sorted_appointments
if sorted_appointments.blank?
render json: { message: 'No appointments found' }, status: :not_found
return
end
+
+ facility_ids = []
+
sorted_appointments.each do |appt|
# if we don't have the information to lookup the clinic, return 'unable to lookup' message
- if unable_to_lookup_facility?(appt)
- log_unable_to_lookup_facility(appt)
- else
- # get the facility details using the location id
- location_id = appt.location_id
- facility = mobile_facility_service.get_facility(location_id)
- log_recent_facility_details(location_id, facility)
-
- # if facility details are not returned, log 'not found' message
- facility.nil? ? log_no_facility_details_found(location_id) : sorted_facilities.push(facility)
- end
+ unable_to_lookup_facility?(appt) ? log_unable_to_lookup_facility(appt) : facility_ids.push(appt.location_id)
+ end
+
+ # remove duplicate facility ids
+ facility_ids = facility_ids.uniq
+ sorted_facilities = []
+
+ facility_ids.each do |facility_id|
+ # get the facility details using the location id
+ facility = mobile_facility_service.get_facility(facility_id)
+ log_recent_facility_details(facility_id, facility)
+
+ # if facility details are not returned, log 'not found' message
+ facility.nil? ? log_no_facility_details_found(facility_id) : sorted_facilities.push(facility)
end
- # remove duplicate clinics
- sorted_facilities = sorted_facilities.uniq
+
+ # Filter facilities
+ sorted_facilities = filter_type_of_care_facilities(sorted_facilities)
render json: FacilitiesSerializer.new(sorted_facilities)
end
@@ -76,6 +82,30 @@ def appointments_service
VAOS::V2::AppointmentsService.new(current_user)
end
+ def filter_type_of_care_facilities(facilities)
+ # Return facilities without filtering if no service id is provided
+ service_id = params[:clinical_service_id]
+ return facilities unless service_id
+
+ # Retrieve facility scheduling configurations
+ facility_ids = facilities.pluck(:id).to_csv(row_sep: nil)
+ configurations = mobile_facility_service.get_scheduling_configurations(facility_ids)&.[](:data)
+ supported_facilities = []
+
+ facilities.each do |facility|
+ # Include facility in results if direct schedule or request is enabled for the given service
+ configuration = configurations&.find { |config| config[:facility_id] == facility[:id] }
+ service_configuration = configuration&.[](:services)&.find { |service| service[:id] == service_id }
+ if service_configuration&.dig(:direct, :enabled) || service_configuration&.dig(:request, :enabled)
+ supported_facilities.push(facility)
+ end
+ end
+
+ log_no_supported_facilities(service_id) if supported_facilities.blank?
+
+ supported_facilities
+ end
+
def log_unable_to_lookup_clinic(appt)
message = ''
if appt.nil?
@@ -110,11 +140,15 @@ def log_unable_to_lookup_facility(appt)
end
def log_no_facility_details_found(location_id)
- Rails.logger.info 'VAOS recent_facilities', "No clinic details found for location: #{location_id}"
+ Rails.logger.info 'VAOS recent_facilities', "No facility details found for location: #{location_id}"
end
def log_recent_facility_details(location_id, facility)
- Rails.logger.info("VAOS recent_facilities details for location: #{location_id} - #{facility.to_json}")
+ Rails.logger.info('VAOS recent_facilities', "Details found for location: #{location_id} - #{facility.to_json}")
+ end
+
+ def log_no_supported_facilities(service_id)
+ Rails.logger.info('VAOS recent_facilities', "No facility supporting #{service_id} service was found.")
end
def unable_to_lookup_facility?(appt)
@@ -126,12 +160,17 @@ def systems_service
end
def mobile_facility_service
- VAOS::V2::MobileFacilityService.new(current_user)
+ @mobile_facility_service ||=
+ VAOS::V2::MobileFacilityService.new(current_user)
end
def location_id
params.require(:location_id)
end
+
+ def recent_facilities_params
+ params.permit(:clinical_service_id)
+ end
end
end
end
diff --git a/modules/vaos/app/docs/vaos/v2/vaos_v2.yaml b/modules/vaos/app/docs/vaos/v2/vaos_v2.yaml
index 75976e2f4a8..c0f8b624710 100644
--- a/modules/vaos/app/docs/vaos/v2/vaos_v2.yaml
+++ b/modules/vaos/app/docs/vaos/v2/vaos_v2.yaml
@@ -63,7 +63,7 @@ paths:
- name: _include
in: query
description:
- A comma delimted list list of the desired appointment statuses to fetch/filter
+ A comma delimited list list of the desired appointment statuses to fetch/filter
by.
schema:
"$ref": "#/components/schemas/AppointmentIncludes"
@@ -71,7 +71,7 @@ paths:
explode: false
responses:
"200":
- description: Retrives a list of clincs for a given facility
+ description: Retrieves a list of clinics for a given facility
content:
application/json:
schema:
@@ -127,7 +127,7 @@ paths:
$ref: "#/components/schemas/CreateAppointmentRequest"
responses:
"201":
- description: Retrives a list of clincs for a given facility
+ description: Retrieves a list of clinics for a given facility
content:
application/json:
schema:
@@ -438,6 +438,13 @@ paths:
"504":
$ref: "#/components/responses/GatewayTimeout"
"/v2/locations/recent_facilities":
+ parameters:
+ - in: query
+ required: false
+ name: clinical_service_id
+ description: The clinical service (type of care) ID used to filter recent facilities. The currently supported/available VAOS clinical service IDs can be discovered from Mobile Facility Service v2.
+ schema:
+ type: string
get:
tags:
- facilities
@@ -514,7 +521,7 @@ paths:
type: integer
responses:
"200":
- description: Retrives a list of clincs for a given facility
+ description: Retrieves a list of clinics for a given facility
content:
application/json:
schema:
@@ -616,7 +623,7 @@ paths:
type: string
responses:
"200":
- description: Retrives a list of available appointment slots for a given clinic
+ description: Retrieves a list of available appointment slots for a given clinic
content:
application/json:
schema:
@@ -802,7 +809,7 @@ paths:
type: string
responses:
"200":
- description: Retrives a list of clincs for a given facility
+ description: Retrieves a list of clinics for a given facility
content:
application/json:
schema:
@@ -1269,7 +1276,7 @@ components:
type: string
practitioners:
type: array
- description: The practioners participating in this appointment.
+ description: The practitioners participating in this appointment.
items:
"$ref": "#/components/schemas/PractitionerIds"
requestedPeriods:
@@ -1355,7 +1362,7 @@ components:
type: array
items:
$ref: '#/components/schemas/CodeableConcept'
- description: The response for a patient scheduing eligibility request.
+ description: The response for a patient scheduling eligibility request.
PatientProviderRelationship:
type: object
required:
diff --git a/modules/vaos/app/services/eps/appointment_service.rb b/modules/vaos/app/services/eps/appointment_service.rb
index 092bcd11d39..f5036e3c631 100644
--- a/modules/vaos/app/services/eps/appointment_service.rb
+++ b/modules/vaos/app/services/eps/appointment_service.rb
@@ -23,5 +23,51 @@ def create_draft_appointment(referral_id:)
{ patientId: patient_id, referralId: referral_id }, headers)
OpenStruct.new(response.body)
end
+
+ ##
+ # Submit an appointment to EPS for booking
+ #
+ # @param appointment_id [String] The ID of the appointment to submit
+ # @param params [Hash] Hash containing required and optional parameters
+ # @option params [String] :network_id The network ID for the appointment
+ # @option params [String] :provider_service_id The provider service ID
+ # @option params [Array] :slot_ids Array of slot IDs for the appointment
+ # @option params [String] :referral_number The referral number
+ # @option params [Hash] :additional_patient_attributes Optional patient details (address, contact info)
+ # @raise [ArgumentError] If any required parameters are missing
+ # @return OpenStruct response from EPS submit appointment endpoint
+ #
+ def submit_appointment(appointment_id, params = {})
+ raise ArgumentError, 'appointment_id is required and cannot be blank' if appointment_id.blank?
+
+ required_params = %i[network_id provider_service_id slot_ids referral_number]
+ missing_params = required_params - params.keys
+
+ raise ArgumentError, "Missing required parameters: #{missing_params.join(', ')}" if missing_params.any?
+
+ payload = build_submit_payload(params)
+
+ response = perform(:post, "/#{config.base_path}/appointments/#{appointment_id}/submit", payload, headers)
+ OpenStruct.new(response.body)
+ end
+
+ private
+
+ def build_submit_payload(params)
+ payload = {
+ networkId: params[:network_id],
+ providerServiceId: params[:provider_service_id],
+ slotIds: params[:slot_ids],
+ referral: {
+ referralNumber: params[:referral_number]
+ }
+ }
+
+ if params[:additional_patient_attributes]
+ payload[:additionalPatientAttributes] = params[:additional_patient_attributes]
+ end
+
+ payload
+ end
end
end
diff --git a/modules/vaos/app/services/eps/provider_service.rb b/modules/vaos/app/services/eps/provider_service.rb
index 5062ee537a4..ab06ea4dcc5 100644
--- a/modules/vaos/app/services/eps/provider_service.rb
+++ b/modules/vaos/app/services/eps/provider_service.rb
@@ -35,6 +35,24 @@ def get_networks
OpenStruct.new(response.body)
end
+ ##
+ # Get drive times from EPS
+ #
+ # @param destinations [Hash] Hash of UUIDs mapped to lat/long coordinates
+ # @param origin [Hash] Hash containing origin lat/long coordinates
+ # @return OpenStruct response from EPS drive times endpoint
+ #
+ def get_drive_times(destinations:, origin:)
+ payload = {
+ destinations: destinations,
+ origin: origin
+ }
+
+ response = perform(:post, "/#{config.base_path}/drive-times", payload, headers)
+ OpenStruct.new(response.body)
+ end
+
+ ##
# Retrieves available slots for a specific provider.
#
# @param provider_id [String] The unique identifier of the provider
diff --git a/modules/vaos/config/routes.rb b/modules/vaos/config/routes.rb
index 4ef914e621e..9f9bb615576 100644
--- a/modules/vaos/config/routes.rb
+++ b/modules/vaos/config/routes.rb
@@ -6,6 +6,8 @@
get '/appointments', to: 'appointments#index'
get '/appointments/:appointment_id', to: 'appointments#show'
put '/appointments/:id', to: 'appointments#update'
+ get '/providers', to: 'providers#index'
+ get '/providers/:provider_id', to: 'providers#show'
get 'community_care/eligibility/:service_type', to: 'cc_eligibility#show'
get '/locations/:location_id/clinics', to: 'clinics#index'
get '/locations/last_visited_clinic', to: 'clinics#last_visited_clinic'
diff --git a/modules/vaos/spec/requests/vaos/v2/locations/clinics_spec.rb b/modules/vaos/spec/requests/vaos/v2/locations/clinics_spec.rb
index 1436226d546..d0dfa999999 100644
--- a/modules/vaos/spec/requests/vaos/v2/locations/clinics_spec.rb
+++ b/modules/vaos/spec/requests/vaos/v2/locations/clinics_spec.rb
@@ -246,11 +246,55 @@
get '/vaos/v2/locations/recent_facilities', headers: inflection_header
expect(response).to have_http_status(:ok)
facility_info = JSON.parse(response.body)['data']
+ expect(facility_info.length).to eq(3)
expect(facility_info[0]['attributes']['id']).to eq('984GA')
expect(facility_info[0]['attributes']['name']).to eq('Middletown VA Clinic')
end
end
end
+
+ it 'filters by successful facility configuration retrieved' do
+ VCR.use_cassette('vaos/v2/systems/get_recent_facilities_200',
+ match_requests_on: %i[method path query], allow_playback_repeats: true) do
+ Timecop.travel(Time.zone.local(2023, 8, 31, 13, 0, 0)) do
+ get '/vaos/v2/locations/recent_facilities?clinical_service_id=primaryCare', headers: inflection_header
+ expect(response).to have_http_status(:ok)
+ facility_info = JSON.parse(response.body)['data']
+ expect(facility_info.length).to eq(2)
+ expect(facility_info[0]['attributes']['id']).to eq('984GA')
+ expect(facility_info[0]['attributes']['name']).to eq('Middletown VA Clinic')
+ end
+ end
+ end
+
+ it 'filters facilities to those that supports service' do
+ VCR.use_cassette('vaos/v2/systems/get_recent_facilities_200',
+ match_requests_on: %i[method path query], allow_playback_repeats: true) do
+ Timecop.travel(Time.zone.local(2023, 8, 31, 13, 0, 0)) do
+ get '/vaos/v2/locations/recent_facilities?clinical_service_id=covid', headers: inflection_header
+ expect(response).to have_http_status(:ok)
+ facility_info = JSON.parse(response.body)['data']
+ expect(facility_info.length).to eq(1)
+ expect(facility_info[0]['attributes']['id']).to eq('983')
+ expect(facility_info[0]['attributes']['name']).to eq('Cheyenne VA Medical Center')
+ end
+ end
+ end
+
+ it 'logs if no facilities supports service' do
+ VCR.use_cassette('vaos/v2/systems/get_recent_facilities_200',
+ match_requests_on: %i[method path query], allow_playback_repeats: true) do
+ Timecop.travel(Time.zone.local(2023, 8, 31, 13, 0, 0)) do
+ allow(Rails.logger).to receive(:info)
+ get '/vaos/v2/locations/recent_facilities?clinical_service_id=amputation', headers: inflection_header
+ expect(response).to have_http_status(:ok)
+ facility_info = JSON.parse(response.body)['data']
+ expect(facility_info.length).to eq(0)
+ expect(Rails.logger).to have_received(:info)
+ .with('VAOS recent_facilities', 'No facility supporting amputation service was found.')
+ end
+ end
+ end
end
context 'on unsuccessful query for appointment within look back limit' do
diff --git a/modules/vaos/spec/services/eps/appointment_service_spec.rb b/modules/vaos/spec/services/eps/appointment_service_spec.rb
index 2169fff4757..38513c39998 100644
--- a/modules/vaos/spec/services/eps/appointment_service_spec.rb
+++ b/modules/vaos/spec/services/eps/appointment_service_spec.rb
@@ -5,28 +5,31 @@
describe Eps::AppointmentService do
subject(:service) { described_class.new(user) }
- let(:icn) { '123ICN' }
let(:user) { double('User', account_uuid: '1234', icn:) }
- let(:successful_appt_response) do
- double('Response', status: 200, body: { 'count' => 1,
- 'appointments' => [
- {
- 'id' => 'test-id',
- 'state' => 'booked',
- 'patientId' => icn
- }
- ] })
- end
- let(:referral_id) { 'test-referral-id' }
- let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) }
+ let(:config) { instance_double(Eps::Configuration) }
+ let(:headers) { { 'Authorization' => 'Bearer token123' } }
+
+ let(:appointment_id) { 'appointment-123' }
+ let(:icn) { '123ICN' }
before do
- allow(Rails.cache).to receive(:fetch).and_return(memory_store)
- Rails.cache.clear
+ allow(config).to receive(:base_path).and_return('api/v1')
+ allow_any_instance_of(Eps::BaseService).to receive_messages(config: config, headers: headers)
end
describe 'get_appointments' do
context 'when requesting appointments for a logged in user' do
+ let(:successful_appt_response) do
+ double('Response', status: 200, body: { 'count' => 1,
+ 'appointments' => [
+ {
+ 'id' => appointment_id,
+ 'state' => 'booked',
+ 'patientId' => icn
+ }
+ ] })
+ end
+
before do
allow_any_instance_of(VAOS::SessionService).to receive(:perform).and_return(successful_appt_response)
end
@@ -59,10 +62,11 @@
end
describe 'create_draft_appointment' do
+ let(:referral_id) { 'test-referral-id' }
let(:successful_draft_appt_response) do
- double('Response', status: 200, body: { 'id' => icn,
+ double('Response', status: 200, body: { 'id' => appointment_id,
'state' => 'draft',
- 'patientId' => 'test-patient-id' })
+ 'patientId' => icn })
end
context 'when creating draft appointment for a given referral_id' do
@@ -77,7 +81,7 @@
end
end
- context 'when the endpoint fails to return appointments' do
+ context 'when the endpoint fails' do
let(:failed_response) do
double('Response', status: 500, body: 'Unknown service exception')
end
@@ -98,4 +102,111 @@
end
end
end
+
+ describe '#submit_appointment' do
+ let(:valid_params) do
+ {
+ network_id: 'network-123',
+ provider_service_id: 'provider-456',
+ slot_ids: ['slot-789'],
+ referral_number: 'REF-001'
+ }
+ end
+
+ context 'with valid parameters' do
+ let(:successful_response) do
+ double('Response', status: 200, body: { 'id' => appointment_id,
+ 'state' => 'draft',
+ 'patientId' => icn })
+ end
+
+ it 'submits the appointment successfully' do
+ expected_payload = {
+ networkId: valid_params[:network_id],
+ providerServiceId: valid_params[:provider_service_id],
+ slotIds: valid_params[:slot_ids],
+ referral: {
+ referralNumber: valid_params[:referral_number]
+ }
+ }
+
+ expect_any_instance_of(VAOS::SessionService).to receive(:perform)
+ .with(:post, "/#{config.base_path}/appointments/#{appointment_id}/submit", expected_payload, kind_of(Hash))
+ .and_return(successful_response)
+
+ exp_response = OpenStruct.new(successful_response.body)
+
+ expect(service.submit_appointment(appointment_id, valid_params)).to eq(exp_response)
+ end
+
+ it 'includes additional patient attributes when provided' do
+ patient_attributes = { name: 'John Doe', email: 'john@example.com' }
+ params_with_attributes = valid_params.merge(additional_patient_attributes: patient_attributes)
+
+ expected_payload = {
+ networkId: valid_params[:network_id],
+ providerServiceId: valid_params[:provider_service_id],
+ slotIds: valid_params[:slot_ids],
+ referral: {
+ referralNumber: valid_params[:referral_number]
+ },
+ additionalPatientAttributes: patient_attributes
+ }
+
+ expect_any_instance_of(VAOS::SessionService).to receive(:perform)
+ .with(:post, "/#{config.base_path}/appointments/#{appointment_id}/submit", expected_payload, kind_of(Hash))
+ .and_return(successful_response)
+
+ service.submit_appointment(appointment_id, params_with_attributes)
+ end
+ end
+
+ context 'with invalid parameters' do
+ it 'raises ArgumentError when appointment_id is nil' do
+ expect { service.submit_appointment(nil, valid_params) }
+ .to raise_error(ArgumentError, 'appointment_id is required and cannot be blank')
+ end
+
+ it 'raises ArgumentError when appointment_id is empty' do
+ expect { service.submit_appointment('', valid_params) }
+ .to raise_error(ArgumentError, 'appointment_id is required and cannot be blank')
+ end
+
+ it 'raises ArgumentError when appointment_id is blank' do
+ expect { service.submit_appointment(' ', valid_params) }
+ .to raise_error(ArgumentError, 'appointment_id is required and cannot be blank')
+ end
+
+ it 'raises ArgumentError when required parameters are missing' do
+ invalid_params = valid_params.except(:network_id)
+
+ expect { service.submit_appointment(appointment_id, invalid_params) }
+ .to raise_error(ArgumentError, /Missing required parameters: network_id/)
+ end
+
+ it 'raises ArgumentError when multiple required parameters are missing' do
+ invalid_params = valid_params.except(:network_id, :provider_service_id)
+
+ expect { service.submit_appointment(appointment_id, invalid_params) }
+ .to raise_error(ArgumentError, /Missing required parameters: network_id, provider_service_id/)
+ end
+ end
+
+ context 'when API returns an error' do
+ let(:response) { double('Response', status: 500, body: 'Unknown service exception') }
+ let(:exception) do
+ Common::Exceptions::BackendServiceException.new(nil, {}, response.status, response.body)
+ end
+
+ before do
+ allow_any_instance_of(VAOS::SessionService).to receive(:perform).and_raise(exception)
+ end
+
+ it 'returns the error response' do
+ expect do
+ service.submit_appointment(appointment_id, valid_params)
+ end.to raise_error(Common::Exceptions::BackendServiceException, /VA900/)
+ end
+ end
+ end
end
diff --git a/modules/vaos/spec/services/eps/provider_service_spec.rb b/modules/vaos/spec/services/eps/provider_service_spec.rb
index c67ae661063..c3d81c5aef1 100644
--- a/modules/vaos/spec/services/eps/provider_service_spec.rb
+++ b/modules/vaos/spec/services/eps/provider_service_spec.rb
@@ -126,6 +126,70 @@
end
end
+ describe 'get_drive_times' do
+ let(:destinations) do
+ {
+ 'provider-123' => {
+ latitude: 40.7128,
+ longitude: -74.0060
+ }
+ }
+ end
+ let(:origin) do
+ {
+ latitude: 40.7589,
+ longitude: -73.9851
+ }
+ end
+
+ context 'when the request is successful' do
+ let(:response) do
+ double('Response', status: 200, body: {
+ 'destinations' => {
+ '00eff3f3-ecfb-41ff-9ebc-78ed811e17f9' => {
+ 'distanceInMiles' => '4',
+ 'driveTimeInSecondsWithTraffic' => '566',
+ 'driveTimeInSecondsWithoutTraffic' => '493',
+ 'latitude' => '-74.12870564772521',
+ 'longitude' => '-151.6240405624497'
+ }
+ },
+ 'origin' => {
+ 'latitude' => '4.627174468915552',
+ 'longitude' => '-88.72187894562788'
+ }
+ })
+ end
+
+ before do
+ allow_any_instance_of(VAOS::SessionService).to receive(:perform).and_return(response)
+ end
+
+ it 'returns the calculated drive times' do
+ result = service.get_drive_times(destinations:, origin:)
+
+ expect(result).to eq(OpenStruct.new(response.body))
+ end
+ end
+
+ context 'when the request fails' do
+ let(:response) { double('Response', status: 500, body: 'Unknown service exception') }
+ let(:exception) do
+ Common::Exceptions::BackendServiceException.new(nil, {}, response.status, response.body)
+ end
+
+ before do
+ allow_any_instance_of(VAOS::SessionService).to receive(:perform).and_raise(exception)
+ end
+
+ it 'raises an error' do
+ expect do
+ service.get_drive_times(destinations:, origin:)
+ end.to raise_error(Common::Exceptions::BackendServiceException, /VA900/)
+ end
+ end
+ end
+
describe '#get_provider_slots' do
let(:provider_id) { '9mN718pH' }
let(:required_params) do
diff --git a/modules/veteran/app/models/veteran/user.rb b/modules/veteran/app/models/veteran/user.rb
index 06906300a0e..d13083ee4c3 100644
--- a/modules/veteran/app/models/veteran/user.rb
+++ b/modules/veteran/app/models/veteran/user.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'bgs_service/local_bgs'
+require 'bgs_service/org_web_service'
# Veteran model
module Veteran
@@ -32,7 +33,7 @@ def current_poa_information
def previous_poa_code
return @previous_poa_code if @previous_poa_code.present?
- poa_history = local_bgs_service.find_poa_history_by_ptcpnt_id(@user.participant_id)
+ poa_history = bgs_org_service.find_poa_history_by_ptcpnt_id(@user.participant_id)
return nil if poa_history[:person_poa_history].blank?
# Sorts previous power of attorneys by begin date
@@ -44,9 +45,9 @@ def previous_poa_code
@previous_poa_code = poa_codes.delete_if { |poa_code| poa_code == current_poa_code }.first
end
- def bgs_service
+ def bgs_org_service
external_key = "#{@user.first_name} #{@user.last_name}"
- @bgs_service ||= BGS::Services.new(
+ @bgs_itf_service ||= ClaimsApi::OrgWebService.new(
external_uid: @user.mpi_icn,
external_key: external_key.presence || @user.mpi_icn
)
diff --git a/modules/veteran/spec/models/veteran/user_spec.rb b/modules/veteran/spec/models/veteran/user_spec.rb
index e973af313e6..f1d91deeb8d 100644
--- a/modules/veteran/spec/models/veteran/user_spec.rb
+++ b/modules/veteran/spec/models/veteran/user_spec.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
require 'rails_helper'
+require 'bgs_service/org_web_service'
describe Veteran::User do
context 'initialization' do
let(:user) { FactoryBot.create(:user, :loa3) }
- let(:ows) { ClaimsApi::LocalBGS }
+ let(:ows) { ClaimsApi::OrgWebService }
it 'initializes from a user' do
VCR.use_cassette('bgs/claimant_web_service/find_poa_by_participant_id') do
diff --git a/modules/veteran/spec/sidekiq/representatives/update_spec.rb b/modules/veteran/spec/sidekiq/representatives/update_spec.rb
index c0cec791c48..7895f38bfce 100644
--- a/modules/veteran/spec/sidekiq/representatives/update_spec.rb
+++ b/modules/veteran/spec/sidekiq/representatives/update_spec.rb
@@ -78,15 +78,11 @@
let(:address_exists) { true }
before do
- Flipper.enable(:va_v3_contact_information_service)
+ allow(Flipper).to receive(:enabled?).with(:va_v3_contact_information_service).and_return(true)
create_flagged_records(flag_type)
allow(VAProfile::V3::AddressValidation::Service).to receive(:new).and_return(double('VAProfile::V3::AddressValidation::Service', candidate: nil)) # rubocop:disable Layout/LineLength
end
- after do
- Flipper.disable(:va_v3_contact_information_service)
- end
-
it "updates the #{flag_type} and the associated flagged records" do
flagged_records =
RepresentationManagement::FlaggedVeteranRepresentativeContactData
@@ -731,14 +727,10 @@ def create_flagged_records(flag_type)
end
before do
- Flipper.enable(:va_v3_contact_information_service)
+ allow(Flipper).to receive(:enabled?).with(:va_v3_contact_information_service).and_return(true)
allow_any_instance_of(VAProfile::V3::AddressValidation::Service).to receive(:candidate).and_return(api_response)
end
- after do
- Flipper.disable(:va_v3_contact_information_service)
- end
-
context 'when JSON parsing fails' do
let(:invalid_json_data) { 'invalid json' }
diff --git a/modules/vye/app/models/vye/user_profile.rb b/modules/vye/app/models/vye/user_profile.rb
index 88accc27449..c2926e685be 100644
--- a/modules/vye/app/models/vye/user_profile.rb
+++ b/modules/vye/app/models/vye/user_profile.rb
@@ -111,14 +111,6 @@ def self.find_and_update_icn(user:)
nil
end
- def check_for_match
- user_profile = self
- attribute_name = %w[ssn_digest file_number_digest icn].find { |a| attribute_changed? a }
- conflict = attribute_name.present?
-
- { user_profile:, conflict:, attribute_name: }
- end
-
def self.produce(attributes)
ssn, file_number, icn = attributes.values_at(:ssn, :file_number, :icn).map(&:presence)
ssn_digest, file_number_digest = [ssn, file_number].map { |value| gen_digest(value) }
@@ -126,7 +118,8 @@ def self.produce(attributes)
user_profile = find_or_build(ssn_digest:, file_number_digest:)
user_profile&.assign_attributes(**assignment)
- user_profile&.check_for_match
+
+ user_profile
end
def self.find_or_build(ssn_digest:, file_number_digest:)
diff --git a/modules/vye/app/sidekiq/vye/midnight_run.rb b/modules/vye/app/sidekiq/vye/midnight_run.rb
index 096299605a4..e2cf30415ef 100644
--- a/modules/vye/app/sidekiq/vye/midnight_run.rb
+++ b/modules/vye/app/sidekiq/vye/midnight_run.rb
@@ -1,11 +1,46 @@
# frozen_string_literal: true
+# rubocop:disable Style/BlockComments
+=begin
+Summary of process - Hopefully, this helps explain what's going on to the newly initialized maintainer of this code
+ Ingress BDN
+ Creates BDNClone
+ Vye::BatchTransfer::BdnChunk.build_chunks and assigns to chunks
+ note that build_chunks is actually in the parent class of BdnChunk, Vye::BatchTransfer::Chunk which is confusing
+ this is because TimsChunk is a child of Vye::BatchTransfer::Chunk and also builds chunks
+
+ instantiates Vye::BatchTransfer::Chunking with parameters filename & block_size as an array
+ splits that array into chunks
+ downloads the file and splits it into chunks
+ passes the array of chunks back to it's caller above
+
+ uploads each chunk to S3 (the actual method for this lives in CloudTransfer)
+
+ imports each chunk (located in BdnChunk proper) into the database
+ deletes from UserInfo any existing rows for this batch under the BdnClone
+ relies on referential integrity rules to delete any existing
+ address changes, awards, & direct deposit changes
+ it also sets the user_info_id to null in any verifications tied to the user profile
+ this has the potential to be a performance bottleneck
+ line by line loads the data via Vye::LoadData.new(...)
+ This snippet
+ UserProfile.transaction do
+ send(source, **records)
+ end
+ is referring to method bdn_feed in this context in this class
+ so essentially it's creating the UserProfile, UserInfo, UserAddress and UserAward rows as needed
+ source is :bdn_feed as defined in BdnChunk
+=end
+# rubocop:enable Style/BlockComments
+
module Vye
class MidnightRun
include Sidekiq::Worker
def perform
+ Rails.logger.info('Vye::MidnightRun starting')
IngressBdn.perform_async
+ Rails.logger.info('Vye::MidnightRun finished')
end
end
end
diff --git a/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn.rb b/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn.rb
index c3aaf046180..9c5bea199e7 100644
--- a/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn.rb
+++ b/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn.rb
@@ -9,6 +9,8 @@ class IngressBdn
def perform
return if Vye::CloudTransfer.holiday?
+ Rails.logger.info('Vye::MidnightRun::IngressBdn: starting')
+
bdn_clone = Vye::BdnClone.create!(transact_date: Time.zone.today)
bdn_clone_id = bdn_clone.id
@@ -24,6 +26,8 @@ def perform
IngressBdnChunk.perform_in((index * 5).seconds, bdn_clone_id, offset, block_size, filename)
end
end
+
+ Rails.logger.info('Vye::MidnightRun::IngressBdn: finished')
end
def on_complete(status, options)
@@ -31,10 +35,10 @@ def on_complete(status, options)
message =
if status.failures.zero?
- "#{self.class.name}: All chunks have ran for BdnClone(#{bdn_clone_id}), there were no failures."
+ "Vye::MidnightRun::IngressBdn: All chunks have ran for BdnClone(#{bdn_clone_id}), there were no failures."
else
<<~MESSAGE
- #{self.class.name}: All chunks have ran for BdnClone(#{bdn_clone_id}),
+ Vye::MidnightRun::IngressBdn: All chunks have ran for BdnClone(#{bdn_clone_id}),
there were #{status.failures} failure(s).
MESSAGE
end
@@ -47,7 +51,7 @@ def on_success(_status, options)
bdn_clone = BdnClone.find(bdn_clone_id)
bdn_clone.update!(is_active: false)
- Rails.logger.info "#{self.class.name}: Ingress completed successfully for BdnClone(#{bdn_clone_id})"
+ Rails.logger.info "Vye::MidnightRun::IngressBdn: Ingress completed successfully for BdnClone(#{bdn_clone_id})"
end
end
end
diff --git a/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn_chunk.rb b/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn_chunk.rb
index 70beaa09788..b3421fa86ae 100644
--- a/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn_chunk.rb
+++ b/modules/vye/app/sidekiq/vye/midnight_run/ingress_bdn_chunk.rb
@@ -6,6 +6,7 @@ class IngressBdnChunk
include Sidekiq::Job
sidekiq_options retry: 3
+ # The load method is in Vye::BatchTransfer::Chunk which BdnChunk inherits
def perform(bdn_clone_id, offset, block_size, filename)
BatchTransfer::BdnChunk.new(bdn_clone_id:, offset:, block_size:, filename:).load
end
diff --git a/modules/vye/app/sidekiq/vye/sundown_sweep.rb b/modules/vye/app/sidekiq/vye/sundown_sweep.rb
index e8fd34b62dc..8f0e15e99d4 100644
--- a/modules/vye/app/sidekiq/vye/sundown_sweep.rb
+++ b/modules/vye/app/sidekiq/vye/sundown_sweep.rb
@@ -5,8 +5,10 @@ class SundownSweep
include Sidekiq::Worker
def perform
+ Rails.logger.info('Vye::SundownSweep starting')
ClearDeactivatedBdns.perform_async
DeleteProcessedS3Files.perform_async
+ Rails.logger.info('Vye::SundownSweep finished')
end
end
end
diff --git a/modules/vye/app/sidekiq/vye/sundown_sweep/clear_deactivated_bdns.rb b/modules/vye/app/sidekiq/vye/sundown_sweep/clear_deactivated_bdns.rb
index e34a009a957..212c58173fe 100644
--- a/modules/vye/app/sidekiq/vye/sundown_sweep/clear_deactivated_bdns.rb
+++ b/modules/vye/app/sidekiq/vye/sundown_sweep/clear_deactivated_bdns.rb
@@ -8,9 +8,9 @@ class ClearDeactivatedBdns
def perform
return if Vye::CloudTransfer.holiday?
- logger.info('Beginning: delete deactivated bdns')
+ logger.info('Vye::SundownSweep::ClearDeactivatedBdns: starting delete deactivated bdns')
Vye::CloudTransfer.delete_inactive_bdns
- logger.info('Finishing: delete deactivated bdns')
+ logger.info('Vye::SundownSweep::ClearDeactivatedBdns: finished delete deactivated bdns')
end
end
end
diff --git a/modules/vye/app/sidekiq/vye/sundown_sweep/delete_processed_s3_files.rb b/modules/vye/app/sidekiq/vye/sundown_sweep/delete_processed_s3_files.rb
index 9dbeb7c57c8..74bf231978b 100644
--- a/modules/vye/app/sidekiq/vye/sundown_sweep/delete_processed_s3_files.rb
+++ b/modules/vye/app/sidekiq/vye/sundown_sweep/delete_processed_s3_files.rb
@@ -7,9 +7,9 @@ class DeleteProcessedS3Files
def perform
return if Vye::CloudTransfer.holiday?
- logger.info('Beginning: remove_aws_files_from_s3_buckets')
+ logger.info('Vye::SundownSweep::DeleteProcessedS3Files: starting remove_aws_files_from_s3_buckets')
Vye::CloudTransfer.remove_aws_files_from_s3_buckets
- logger.info('Finishing: remove_aws_files_from_s3_buckets')
+ logger.info('Vye::SundownSweep::DeleteProcessedS3Files: finished remove_aws_files_from_s3_buckets')
end
end
end
diff --git a/modules/vye/lib/concerns/vye/cloud_transfer.rb b/modules/vye/lib/concerns/vye/cloud_transfer.rb
index 1750822b4ef..1fa7a39d34d 100644
--- a/modules/vye/lib/concerns/vye/cloud_transfer.rb
+++ b/modules/vye/lib/concerns/vye/cloud_transfer.rb
@@ -33,24 +33,53 @@ def tmp_dir
def tmp_path(filename) = tmp_dir / filename
def download(filename, prefix: 'scanned')
+ Rails.logger.info("Vye::BatchTransfer::Chunk#download: starting for #{filename}")
response_target = tmp_path filename
key = "#{prefix}/#{filename}"
- s3_client.get_object(response_target:, bucket:, key:)
+ Rails.logger.info(
+ "Vye::BatchTransfer::Chunk#download: s3_client.get_object(#{response_target}, #{bucket}, #{key})"
+ )
+
+ if Settings.vsp_environment.eql?('localhost')
+ FileUtils.cp(
+ Rails.root.join('modules', 'vye', 'spec', 'fixtures', 'bdn_sample', filename), response_target
+ )
+ else
+ s3_client.get_object(response_target:, bucket:, key:)
+ end
yield response_target
ensure
- response_target.delete
+ # There's some rooted in the framework bug that will try to delete the file after it
+ # has already been deleted. Ignore the exception and move on.
+ begin
+ response_target&.delete
+ rescue Errno::ENOENT
+ nil
+ ensure
+ Rails.logger.info('Vye::BatchTransfer::Chunk#download: finished')
+ end
end
def upload(file, prefix: 'processed')
+ return if Settings.vsp_environment.eql?('localhost')
+
+ Rails.logger.info("Vye::BatchTransfer::Chunk#upload: starting for #{file}, #{prefix}")
+
key = "#{prefix}/#{file.basename}"
body = file.open('rb')
content_type = 'text/plain'
s3_client.put_object(bucket:, key:, body:, content_type:)
ensure
- body.close
+ begin
+ body&.close
+ rescue Errno::ENOENT
+ nil
+ ensure
+ Rails.logger.info('Vye::BatchTransfer::Chunk#upload: finished')
+ end
end
def upload_report(filename, &)
@@ -62,6 +91,7 @@ def upload_report(filename, &)
end
def clear_from(bucket_sym: :internal, path: 'processed')
+ Rail.logger.info "Vye::SundownSweep::DeleteProcessedS3Files#clear_from(#{bucket_sym}, #{path})"
bucket = { internal: self.bucket, external: external_bucket }[bucket_sym]
prefix = "#{path}/"
check_s3_location!(bucket:, path:)
@@ -114,6 +144,10 @@ def upload_fixtures
def remove_aws_files_from_s3_buckets
# remove from the scanned bucket
[Vye::BatchTransfer::TimsChunk::FEED_FILENAME, Vye::BatchTransfer::BdnChunk::FEED_FILENAME].each do |filename|
+ Rails.logger.info(
+ "Vye::SundownSweep::DeleteProcessedS3Files#remove_aws_files_from_s3_buckets deleting #{filename}"
+ )
+
delete_file_from_bucket(:internal, "scanned/#{filename}")
end
@@ -129,12 +163,23 @@ def remove_aws_files_from_s3_buckets
def delete_inactive_bdns
bdn_clone_ids = Vye::BdnClone.where(is_active: nil, export_ready: nil).pluck(:id)
bdn_clone_ids.each do |bdn_clone_id|
+ Rails.logger.info(
+ "Vye::SundownSweep::ClearDeactivatedBdns#delete_inactive_bdns: processing BdnClone(#{bdn_clone_id})"
+ )
+
+ Rails.logger.info('Vye::SundownSweep::ClearDeactivatedBdns#delete_inactive_bdns: deleting DirectDepositChanges')
Vye::DirectDepositChange.joins(:user_info).where(vye_user_infos: { bdn_clone_id: }).in_batches.delete_all
+ Rails.logger.info('Vye::SundownSweep::ClearDeactivatedBdns#delete_inactive_bdns: deleting AddressChanges')
Vye::AddressChange.joins(:user_info).where(vye_user_infos: { bdn_clone_id: }).in_batches.delete_all
+ Rails.logger.info('Vye::SundownSweep::ClearDeactivatedBdns#delete_inactive_bdns: deleting Awards')
Vye::Award.joins(:user_info).where(vye_user_infos: { bdn_clone_id: }).in_batches.delete_all
# We're not worried about validations here because it wouldn't be in the table if it wasn't valid
# rubocop:disable Rails/SkipsModelValidations
+ Rails.logger.info(
+ 'Vye::SundownSweep::ClearDeactivatedBdns#delete_inactive_bdns: nullifying verification references'
+ )
+
Vye::Verification
.joins(:user_info)
.where(vye_user_infos: { bdn_clone_id: })
@@ -143,9 +188,11 @@ def delete_inactive_bdns
# rubocop:enable Rails/SkipsModelValidations
# nuke user infos
+ Rails.logger.info('Vye::SundownSweep::ClearDeactivatedBdns#delete_inactive_bdns: deleting UserInfos')
Vye::UserInfo.where(bdn_clone_id:).delete_all
# nuke bdn_clone
+ Rails.logger.info('Vye::SundownSweep::ClearDeactivatedBdns#delete_inactive_bdns: deleting BdnClone')
Vye::BdnClone.find(bdn_clone_id).destroy
end
end
diff --git a/modules/vye/lib/tasks/vye/setup_for_sundown_sweep_devtest.rake b/modules/vye/lib/tasks/vye/setup_for_sundown_sweep_devtest.rake
new file mode 100644
index 00000000000..e5a3a4a9d5e
--- /dev/null
+++ b/modules/vye/lib/tasks/vye/setup_for_sundown_sweep_devtest.rake
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+# this task creates database rows to run sundown sweep in a local development sandbox
+# Look at the BdnClone model for details on the various states. There's a matrix at the top
+
+# To run the process in a manner that more or less closely resembles prodcution, do the following:
+# 1) In a terminal window at the vets-api root, run: foreman start -m all=1,clamd=0,freshclam=0
+# 2) Open up a browser and navigate to http://localhost:3000/sidekiq/busy
+# You can see the job queue there and this job when it runs
+# 3) In another terminal window at the vets-api root, run this rake task: rake vye:create_sundown_sweep_dev_sandbox_data
+# This creates the necessary database rows to run sundown sweep in a local development sandbox
+# 4) Once step 3 is complete, run rails c to start a rails console
+# You can run the sundown sweep job in the console with: Vye::SundownSweepJob.new.perform
+namespace :vye do
+ desc 'create database rows to run sundown sweep in a local development sandbox'
+ task create_sundown_sweep_dev_sandbox_data: :environment do |_cmd, _args|
+ # clear out the log file
+ Rake::Task['log:clear'].invoke
+
+ puts 'Clearing out BdnClone'
+ Vye::BdnClone.destroy_all
+ puts 'Clearing out related table data for Sundown Sweep test'
+ Vye::Verification.destroy_all
+ Vye::PendingDocument.destroy_all
+
+ # this should blow away the address changes, awards, & direct deposit changes
+ # via referential integrity delete cascade rules
+ # we already blew away the verifications
+ Vye::UserInfo.destroy_all
+
+ # You have to provide a value for scrypt.salt or you get an error trying to create a UserProfile
+ Vye.settings.scrypt.salt = '1'
+
+ puts 'Creating BdnClone row that will be deleted'
+ FactoryBot.create(:vye_bdn_clone_with_user_info_children, transact_date: Time.Zone.today - 2.days)
+
+ puts 'Creating active BdnClone row'
+ FactoryBot.create(
+ :vye_bdn_clone_with_user_info_children, :active, transact_date: Time.Zone.today - 1.day
+ )
+
+ puts 'Creating freshly imported BdnClone row'
+ FactoryBot.create(:vye_bdn_clone_with_user_info_children, is_active: false)
+
+ puts 'BdnClones created'
+ Vye::BdnClone.all.find_each { |bdn_clone| puts bdn_clone.inspect }
+
+ puts "\nUserProfiles created"
+ Vye::UserProfile.all.find_each { |user_profile| puts user_profile.inspect }
+
+ puts "\nUserInfos created"
+ Vye::UserInfo.all.find_each { |user_info| puts user_info.inspect }
+
+ puts "\nPendingDocuments created"
+ Vye::PendingDocument.all.find_each { |pending_document| puts pending_document.inspect }
+
+ puts "\nVerifications created"
+ Vye::Verification.all.find_each { |verification| puts verification.inspect }
+
+ puts "\nAddressChanges created"
+ Vye::AddressChange.all.find_each { |address_change| puts address_change.inspect }
+
+ puts "\nAwards created"
+ Vye::Award.all.find_each { |award| puts award.inspect }
+
+ puts "\nDirectDepositChanges created"
+ Vye::DirectDepositChange.all.find_each { |direct_deposit_change| puts direct_deposit_change.inspect }
+ end
+end
diff --git a/modules/vye/lib/vye/batch_transfer/bdn_chunk.rb b/modules/vye/lib/vye/batch_transfer/bdn_chunk.rb
index dbb76440eb1..c3c0d891405 100644
--- a/modules/vye/lib/vye/batch_transfer/bdn_chunk.rb
+++ b/modules/vye/lib/vye/batch_transfer/bdn_chunk.rb
@@ -10,8 +10,12 @@ class BdnChunk < Vye::BatchTransfer::Chunk
def self.feed_filename = FEED_FILENAME
def initialize(bdn_clone_id:, offset:, block_size:, filename:)
+ Rails.logger.info('Vye::BatchTransfer::BdnChunk#initialize: starting')
+
@bdn_clone = Vye::BdnClone.find(bdn_clone_id)
super(offset:, block_size:, filename:)
+
+ Rails.logger.info('Vye::BatchTransfer::BdnChunk#initialize: finished')
end
def import
diff --git a/modules/vye/lib/vye/batch_transfer/chunk.rb b/modules/vye/lib/vye/batch_transfer/chunk.rb
index c1a45732e28..990a70d9149 100644
--- a/modules/vye/lib/vye/batch_transfer/chunk.rb
+++ b/modules/vye/lib/vye/batch_transfer/chunk.rb
@@ -16,16 +16,23 @@ def self.feed_filename
end
def self.build_chunks
+ Rails.logger.info('Vye::BatchTransfer::Chunk#build_chunks: starting')
filename = feed_filename
chunking = Vye::BatchTransfer::Chunking.new(filename:, block_size:)
chunks = chunking.split
chunks.each(&:upload)
+ Rails.logger.info('Vye::BatchTransfer::Chunk#build_chunks: returning chunks')
chunks
end
def initialize(offset:, block_size:, file: nil, filename: nil)
+ Rails.logger.info(
+ "Vye::BatchTransfer::Chunk#initialize: offset=#{offset}, block_size=#{block_size}, file=#{file}, " \
+ "filename=#{filename}"
+ )
+
raise ArgumentError, "can't have both a file and filename" if file && filename
raise ArgumentError, 'must have either a file or a filename' unless file || filename
@@ -33,6 +40,7 @@ def initialize(offset:, block_size:, file: nil, filename: nil)
@block_size = block_size
@file = file
@filename = filename
+ Rails.logger.info('Vye::BatchTransfer::Chunk#initialize: finished')
end
def prefix = 'chunks'
@@ -41,6 +49,7 @@ def filename
@filename || file.basename
end
+ # Upload is in Vye::CloudTransfer
def upload
raise ArgumentError, 'must have a file to upload' unless file
@@ -59,11 +68,16 @@ def import
raise NotImplementedError
end
+ # Download is in Vye::CloudTransfer
def load
+ Rails.logger.info('Vye::BatchTransfer::Chunk#load: starting')
+
download do |file|
@file = file
import
end
+
+ Rails.logger.info('Vye::BatchTransfer::Chunk#load: finished')
end
end
end
diff --git a/modules/vye/lib/vye/batch_transfer/chunking.rb b/modules/vye/lib/vye/batch_transfer/chunking.rb
index ab7242084dd..b805b1b1583 100644
--- a/modules/vye/lib/vye/batch_transfer/chunking.rb
+++ b/modules/vye/lib/vye/batch_transfer/chunking.rb
@@ -8,6 +8,8 @@ class NotReadyForUploading < StandardError; end
include Vye::CloudTransfer
def initialize(filename:, block_size:)
+ Rails.logger.info("Vye::BatchTransfer::Chunking#initialize: filename=#{filename}, block_size=#{block_size}")
+
@filename = filename
@block_size = block_size
@stem, @ext =
@@ -18,9 +20,11 @@ def initialize(filename:, block_size:)
end
@chunks = []
@flags = %i[split].index_with { |_f| false }
+ Rails.logger.info('Vye::BatchTransfer::Chunking#initialize complete')
end
def split
+ Rails.logger.info('Vye::BatchTransfer::Chunking#split starting')
return chunks if split?
download(filename) do |path|
@@ -28,9 +32,14 @@ def split
end
split!
+
chunks
+ rescue => e
+ Rails.logger.error("Error splitting chunks: #{e.message}")
+ nil
ensure
close_current_handle
+ Rails.logger.info('Vye::BatchTransfer::Chunking#split complete')
end
private
diff --git a/modules/vye/lib/vye/load_data.rb b/modules/vye/lib/vye/load_data.rb
index 6f7ead572a3..38b9ad4728f 100644
--- a/modules/vye/lib/vye/load_data.rb
+++ b/modules/vye/lib/vye/load_data.rb
@@ -1,12 +1,28 @@
# frozen_string_literal: true
module Vye
- class UserProfileConflict < RuntimeError; end
- class UserProfileNotFound < RuntimeError; end
-
class LoadData
+ STATSD_PREFIX = name.gsub('::', '.').underscore
+ STATSD_NAMES = {
+ failure: "#{STATSD_PREFIX}.failure.no_source",
+ team_sensitive_failure: "#{STATSD_PREFIX}.failure.team_sensitive",
+ tims_feed_failure: "#{STATSD_PREFIX}.failure.tims_feed",
+ bdn_feed_failure: "#{STATSD_PREFIX}.failure.bdn_feed",
+ user_profile_created: "#{STATSD_PREFIX}.user_profile.created",
+ user_profile_updated: "#{STATSD_PREFIX}.user_profile.updated",
+ user_profile_creation_skipped: "#{STATSD_PREFIX}.user_profile.creation_skipped",
+ user_profile_update_skipped: "#{STATSD_PREFIX}.user_profile.update_skipped"
+ }.freeze
+
SOURCES = %i[team_sensitive tims_feed bdn_feed].freeze
+ FAILURE_TEMPLATE = <<~FAILURE_TEMPLATE_HEREDOC.gsub(/\n/, ' ').freeze
+ Loading data failed:
+ source: %