diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7ac7c4f7119..771ab9aa856 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -623,6 +623,7 @@ app/uploaders/vets_shrine.rb @department-of-veterans-affairs/va-api-engineers @d app/validators/token_util.rb @department-of-veterans-affairs/backend-review-group app/uploaders/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 app/sidekiq/account_login_statistics_job.rb @department-of-veterans-affairs/octo-identity +app/sidekiq/accredited_representative_portal/power_of_attorney_form_submission_job.rb @department-of-veterans-affairs/benefits-accredited-rep-facing-engineers @department-of-veterans-affairs/backend-review-group app/sidekiq/benefits_intake_remediation_status_job.rb @department-of-veterans-affairs/platform-va-product-forms @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group 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 diff --git a/app/sidekiq/accredited_representative_portal/power_of_attorney_form_submission_job.rb b/app/sidekiq/accredited_representative_portal/power_of_attorney_form_submission_job.rb new file mode 100644 index 00000000000..1c41cdf3740 --- /dev/null +++ b/app/sidekiq/accredited_representative_portal/power_of_attorney_form_submission_job.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'sentry_logging' + +module AccreditedRepresentativePortal + class PowerOfAttorneyFormSubmissionJob + include Sidekiq::Job + include SentryLogging + + sidekiq_options retry: 2 + + attr_reader :response + + def perform(poa_form_submission_id) + @id = poa_form_submission_id + service = BenefitsClaims::Service.new(poa_form_submission.power_of_attorney_request.claimant.icn) + @response = service.get_2122_submission(poa_form_submission.service_id) + poa_form_submission.update( + status: (non_error_response? ? :succeeded : :failed), + service_response: response.to_json, + status_updated_at: DateTime.current, + error_message: error_data.to_json + ) + rescue => e + handle_errors(e, poa_form_submission) + end + + sidekiq_retries_exhausted do |job, _ex| + poa_form_submission_id = job['args'].first + poa_form_submission = PowerOfAttorneyFormSubmission.find(poa_form_submission_id) + poa_form_submission.update(status: :failed, status_updated_at: DateTime.current) + end + + def handle_errors(e, poa_form_submission) + log_exception_to_sentry(e) + poa_form_submission.update(error_message: e.message, status: :enqueue_failed) + raise e + end + + def non_error_response? + response.dig('data', 'attributes', 'status') != 'errored' + end + + def error_data + response.dig('data', 'attributes', 'errors') + end + + def poa_form_submission + @poa_form_submission ||= PowerOfAttorneyFormSubmission.find(@id) + end + end +end diff --git a/modules/accredited_representative_portal/app/services/accredited_representative_portal/power_of_attorney_request_service/accept.rb b/modules/accredited_representative_portal/app/services/accredited_representative_portal/power_of_attorney_request_service/accept.rb index 5e2409d8030..e4c017a1454 100644 --- a/modules/accredited_representative_portal/app/services/accredited_representative_portal/power_of_attorney_request_service/accept.rb +++ b/modules/accredited_representative_portal/app/services/accredited_representative_portal/power_of_attorney_request_service/accept.rb @@ -38,8 +38,8 @@ def call service_id: response.body.dig('data', 'id'), service_response: response.body.to_json ) + PowerOfAttorneyFormSubmissionJob.perform_async(form_submission.id) form_submission - # TODO: call PowerOfAttorneyFormSubmissionJob.perform_async(poa_form_submission) # Invalid record - return error message with 400 rescue ActiveRecord::RecordInvalid => e raise Error.new(e.message, :bad_request) diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_form_submission.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_form_submission.rb new file mode 100644 index 00000000000..42775b2bb5e --- /dev/null +++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_form_submission.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :power_of_attorney_form_submission, + class: 'AccreditedRepresentativePortal::PowerOfAttorneyFormSubmission' do + association :power_of_attorney_request + service_id { SecureRandom.uuid } + service_response { '{}' } + status { :enqueue_succeeded } + status_updated_at { DateTime.now } + end +end diff --git a/modules/accredited_representative_portal/spec/fixtures/power_of_attorney_form_submission/error.json b/modules/accredited_representative_portal/spec/fixtures/power_of_attorney_form_submission/error.json new file mode 100644 index 00000000000..a9c5490fd9e --- /dev/null +++ b/modules/accredited_representative_portal/spec/fixtures/power_of_attorney_form_submission/error.json @@ -0,0 +1,45 @@ +{ + "data": { + "id": "a4dd5bb0-d47b-4ca9-9afc-d7ce6a820985", + "type": "claimsApiPowerOfAttorneys", + "attributes": { + "dateRequestAccepted": "2025-01-16", + "previousPoa": "-", + "representative": { + "veteran": { + "address": { + "addressLine1": "123", + "addressLine2": "2a", + "city": "city", + "countryCode": "US", + "stateCode": "OR", + "zipCode": "12345", + "zipCodeSuffix": "6789" + } + }, + "representative": { + "poaCode": "067", + "registrationNumber": "999999999999", + "type": "ATTORNEY", + "address": { + "addressLine1": "123", + "addressLine2": "2a", + "city": "city", + "countryCode": "US", + "stateCode": "OR", + "zipCode": "12345", + "zipCodeSuffix": "6789" + } + } + }, + "status": "errored", + "errors": [ + { + "title": "some error", + "detail": "error detail", + "code": "PDF_SUBMISSION" + } + ] + } + } +} diff --git a/modules/accredited_representative_portal/spec/fixtures/power_of_attorney_form_submission/success.json b/modules/accredited_representative_portal/spec/fixtures/power_of_attorney_form_submission/success.json new file mode 100644 index 00000000000..80bcbcd0dd4 --- /dev/null +++ b/modules/accredited_representative_portal/spec/fixtures/power_of_attorney_form_submission/success.json @@ -0,0 +1,38 @@ +{ + "data": { + "id": "a4dd5bb0-d47b-4ca9-9afc-d7ce6a820985", + "type": "claimsApiPowerOfAttorneys", + "attributes": { + "dateRequestAccepted": "2025-01-16", + "previousPoa": "-", + "representative": { + "veteran": { + "address": { + "addressLine1": "123", + "addressLine2": "2a", + "city": "city", + "countryCode": "US", + "stateCode": "OR", + "zipCode": "12345", + "zipCodeSuffix": "6789" + } + }, + "representative": { + "poaCode": "067", + "registrationNumber": "999999999999", + "type": "ATTORNEY", + "address": { + "addressLine1": "123", + "addressLine2": "2a", + "city": "city", + "countryCode": "US", + "stateCode": "OR", + "zipCode": "12345", + "zipCodeSuffix": "6789" + } + } + }, + "status": "pending" + } + } +} diff --git a/spec/sidekiq/accredited_representative_portal/power_of_attorney_form_submission_job_spec.rb b/spec/sidekiq/accredited_representative_portal/power_of_attorney_form_submission_job_spec.rb new file mode 100644 index 00000000000..e12f4faac02 --- /dev/null +++ b/spec/sidekiq/accredited_representative_portal/power_of_attorney_form_submission_job_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'sidekiq/testing' + +RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyFormSubmissionJob, type: :job do + subject { described_class.new } + + let(:poa_form_submission) do + create(:power_of_attorney_form_submission, service_id: '29b7c214-4a61-425e-97f2-1a56de869524') + end + + before do + allow_any_instance_of(Auth::ClientCredentials::Service).to receive(:get_token).and_return('fake_access_token') + poa_form_submission.power_of_attorney_request.claimant.update(icn: '123498767V234859') + end + + describe '#perform' do + context 'successful LH submission' do + let(:service_response) do + File.read('modules/accredited_representative_portal/spec' \ + '/fixtures/power_of_attorney_form_submission/success.json') + end + + context 'successful submission' do + it 'updates the form submission as successful' do + VCR.use_cassette('lighthouse/benefits_claims/power_of_attorney_status/200_response') do + subject.perform(poa_form_submission.id) + end + poa_form_submission.reload + expect(poa_form_submission.status).to eq 'succeeded' + expect(JSON.parse(poa_form_submission.service_response)).to eq JSON.parse(service_response) + expect(poa_form_submission.status_updated_at).not_to be_nil + end + + context 'data shows status of errored' do + let(:expected_error_message) do + '[{"title":"some error","detail":"error detail","code":"PDF_SUBMISSION"}]' + end + let(:service_response) do + File.read('modules/accredited_representative_portal/spec' \ + '/fixtures/power_of_attorney_form_submission/error.json') + end + + it 'updates the form submission as failed' do + VCR.use_cassette('lighthouse/benefits_claims/power_of_attorney_status/200_errored_response') do + subject.perform(poa_form_submission.id) + end + poa_form_submission.reload + expect(poa_form_submission.status).to eq 'failed' + expect(JSON.parse(poa_form_submission.service_response)).to eq JSON.parse(service_response) + expect(poa_form_submission.error_message).to eq expected_error_message + expect(poa_form_submission.status_updated_at).not_to be_nil + end + end + + context 'the job fails 3 times' do + let(:lh_service) { double } + + it 'updates the status as failed' do + subject.sidekiq_retries_exhausted_block.call({ 'args' => [poa_form_submission.id] }) + poa_form_submission.reload + expect(poa_form_submission.status).to eq('failed') + end + end + end + end + + context 'submission not found' do + let(:rails_logger) { double } + + it 'updates the form submission and submits the error to sentry and logger' do + Settings.sentry = OpenStruct.new(dsn: 'test') + allow(Rails).to receive(:logger).and_return rails_logger + poa_form_submission.update(service_id: '491b878a-d977-40b8-8de9-7ba302307a48') + expect(Sentry).to receive(:capture_exception) + expect(rails_logger).to receive(:send).with('error', 'Resource not found.') + expect(rails_logger).to receive(:send) + expect do + VCR.use_cassette('lighthouse/benefits_claims/power_of_attorney_status/404_response') do + subject.perform(poa_form_submission.id) + end + end.to raise_error(Common::Exceptions::ResourceNotFound) + poa_form_submission.reload + expect(poa_form_submission.error_message).to eq 'Resource not found' + expect(poa_form_submission.status_updated_at).not_to be_nil + end + end + end +end diff --git a/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney_status/200_errored_response.yml b/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney_status/200_errored_response.yml new file mode 100644 index 00000000000..de6c328e5ec --- /dev/null +++ b/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney_status/200_errored_response.yml @@ -0,0 +1,77 @@ +--- +http_interactions: +- request: + method: get + uri: "/services/claims/v2/veterans/123498767V234859/power-of-attorney/29b7c214-4a61-425e-97f2-1a56de869524" + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Vets.gov Agent + Authorization: Bearer + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Thu, 16 Jan 2025 16:16:37 GMT + Content-Type: + - application/json; charset=utf-8 + Connection: + - keep-alive + Ratelimit-Remaining: + - '119' + Ratelimit-Reset: + - '24' + X-Ratelimit-Limit-Minute: + - '120' + X-Ratelimit-Remaining-Minute: + - '119' + Ratelimit-Limit: + - '120' + Etag: + - W/"67b28006fd457f0098fd70638e6794e8" + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + - SAMEORIGIN + X-Git-Sha: + - 1ba326afd549925de35cadf38f116636816403c7 + X-Github-Repository: + - https://github.com/department-of-veterans-affairs/vets-api + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - c9890add-12b4-46c4-86f7-a6401c91bee3 + X-Runtime: + - '0.425435' + X-Xss-Protection: + - '0' + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Cache-Control: + - no-cache, no-store + Pragma: + - no-cache + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: '{"data":{"id":"a4dd5bb0-d47b-4ca9-9afc-d7ce6a820985","type":"claimsApiPowerOfAttorneys","attributes":{"dateRequestAccepted":"2025-01-16","previousPoa":"-","representative":{"veteran":{"address":{"addressLine1":"123","addressLine2":"2a","city":"city","countryCode":"US","stateCode":"OR","zipCode":"12345","zipCodeSuffix":"6789"}},"representative":{"poaCode":"067","registrationNumber":"999999999999","type":"ATTORNEY","address":{"addressLine1":"123","addressLine2":"2a","city":"city","countryCode":"US","stateCode":"OR","zipCode":"12345","zipCodeSuffix":"6789"}}},"status":"errored", "errors":[{"title":"some error","detail":"error detail","code":"PDF_SUBMISSION"}]}}}' + recorded_at: Thu, 16 Jan 2025 16:16:37 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney_status/200_response.yml b/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney_status/200_response.yml index e254a142d33..ab04d378b38 100644 --- a/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney_status/200_response.yml +++ b/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney_status/200_response.yml @@ -1,58 +1,5 @@ --- http_interactions: -- request: - method: post - uri: "/oauth2/claims/system/v1/token" - body: - encoding: US-ASCII - string: grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiIwb2ExMXN6M3ByeExhNjU0RzJwOCIsInN1YiI6IjBvYTExc3ozcHJ4TGE2NTRHMnA4IiwiYXVkIjoiaHR0cHM6Ly9kZXB0dmEtZXZhbC5va3RhLmNvbS9vYXV0aDIvYXVzZGc3Z3VpczJUWURsRmUycDcvdjEvdG9rZW4iLCJpYXQiOjE3MzcwNDQxOTUsImV4cCI6MTczNzA0NDQ5NX0.qFw7mxw1RSxXvSc1tIPm78dRAuE5R3IRbL1Ojzs1q_x-90ybLNyTjXeqjhP_OIO8r3W4sLzvRXU73a20bWVrVT7501pHXqKBF-Z_J4bjkinV6YetRkAXKE67EchgzQ0H4GhWbhINRl3ZV4AvYEXYp3I7v7L76awfQ54jepTwjm5hHtqPn9RHEJ7CsXM3AnXQgszA6rHvPLp7tHTlhX3vofJwEBslJM8xzAclE68z4iodDiiqAImsykayEPk3kP7YIpLEOjHc5RUDUrHPtB-gyBnN8CwLpjL24l98iMRDGgZRm4kDH5Wg4OCZ_J6ehThOkSelVcQ3N00RFyyYfTfSqg&scope=system%2Fclaim.read+system%2Fclaim.write+system%2F526-pdf.override+system%2F526.override - headers: - Accept: - - application/json - Content-Type: - - application/x-www-form-urlencoded - User-Agent: - - Vets.gov Agent - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - response: - status: - code: 200 - message: OK - headers: - Date: - - Thu, 16 Jan 2025 16:16:36 GMT - Content-Type: - - application/json; charset=utf-8 - Connection: - - keep-alive - X-Ratelimit-Remaining-Minute: - - '238' - Ratelimit-Limit: - - '240' - Ratelimit-Remaining: - - '238' - Ratelimit-Reset: - - '25' - X-Ratelimit-Limit-Minute: - - '240' - Vary: - - Origin - Cache-Control: - - no-cache, no-store - Pragma: - - no-cache - Etag: - - W/"462-ePRIWe2RWQWPe0qc1THMTYGalj8" - Access-Control-Allow-Origin: - - "*" - Transfer-Encoding: - - chunked - body: - encoding: ASCII-8BIT - string: '{"access_token":"eyJraWQiOiJ0V3RPY1lURUpqVEl1UmdxX1lJZnFEVndjXzVXTWszUGM3VHVsc2luVlJ3IiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULnRWNG5PM1NsYk1xdnJqOUE1bDZ6ZWNOQ0dWeDlKZlB6aUQ0NmhRdGZlUVEiLCJpc3MiOiJodHRwczovL2RlcHR2YS1ldmFsLm9rdGEuY29tL29hdXRoMi9hdXNkZzdndWlzMlRZRGxGZTJwNyIsImF1ZCI6Imh0dHBzOi8vc2FuZGJveC1hcGkudmEuZ292L3NlcnZpY2VzL2NsYWltcyIsImlhdCI6MTczNzA0NDE5NiwiZXhwIjoxNzM3MDQ0NDk2LCJjaWQiOiIwb2ExMXN6M3ByeExhNjU0RzJwOCIsInNjcCI6WyJzeXN0ZW0vNTI2Lm92ZXJyaWRlIiwic3lzdGVtLzUyNi1wZGYub3ZlcnJpZGUiLCJzeXN0ZW0vY2xhaW0ud3JpdGUiLCJzeXN0ZW0vY2xhaW0ucmVhZCJdLCJzdWIiOiIwb2ExMXN6M3ByeExhNjU0RzJwOCIsImxhYmVsIjoiV2lsY29yZUhlZHJpY2sifQ.R0S1pcm4Bh-4wU0yVCwJrk5ffOfl-iKud0SpjGrOzXEH9ug36E0w6dQPE6vq5FJDxIwcMn1ir3HLBZjKQObsClw7K12M5wGTnBQ9K93LikZSz4gV26NVBftuNKmj3c6WtgG4Quzyuxe3Minz7bULKLH5KcV4LLF0L0ygFBgY-d_fxY2P8iPGNuCZlAKjlMUZDafr3Ee5YNZfYx5NE83FonkQ0RlIpPhCiYxBXD6aEY8qftuuOLGtgK3HQ1GhVqnqex4ZYdVB9BkJWMGH8qR4zP-J3FMYMa54Li4VRcS9lM2t0x1z22iz5pEEwms89j9TsouruYcZD6o77Tt8x7GHaQ","token_type":"Bearer","scope":"system/526.override - system/526-pdf.override system/claim.write system/claim.read","expires_in":300,"state":null}' - recorded_at: Thu, 16 Jan 2025 16:16:36 GMT - request: method: get uri: "/services/claims/v2/veterans/123498767V234859/power-of-attorney/29b7c214-4a61-425e-97f2-1a56de869524"