From 62e01b6adc38fe1ff012fa71bb56237c94762b3b Mon Sep 17 00:00:00 2001 From: Holden Hinkle Date: Mon, 8 Apr 2024 11:10:18 -0400 Subject: [PATCH 1/5] Hh 77606 create poa endpoint (#16132) * create poa service and endpoint * wip * create #get_power_of_attorney in BenefitsClaims::Service * refactor PowerOfAttorneyController * delete RepresentationManagement::PowerOfAttorney::Service * create power_of_attorney route * add test for BenefitsClaims::Service #get_power_of_attorney * add new vcr cassette for power_of_attorney (must be updated) * write request specs for PowerOfAttorneyController * remove email from base serializer and add it to rep serializer * delete 'PowerOfAttorneyController', type: :request spec * create RepresentationManagement::V0::PowerOfAttorneyController, type: :controller spec instead * update endpoint response * formatting * add more tests, refactor * add serializer tests * add error handling to PowerOfAttorneyController #index * update icn used in testing poa endpoint * record vcr * remove settings * convert dig args to strings * order rep query by created_at DESC * correct the test name * fix failing tests * use #order in #find_representative * stub #order in test * remove private tests * refactor PowerOfAttorneyController * wip * add another test for rep poa response * refactor controller and add empty response vcr * add test for lighthouse responding with 422 * add request spec test for lighthouse responding with 422 * remove benefits claims service poa 422 response test --------- Co-authored-by: Jonathan VanCourt --- lib/lighthouse/benefits_claims/service.rb | 8 ++ .../v0/power_of_attorney_controller.rb | 65 +++++++++ .../power_of_attorney/base_serializer.rb | 21 +++ .../organization_serializer.rb | 14 ++ .../representative_serializer.rb | 23 +++ .../config/routes.rb | 1 + .../v0/power_of_attorney_request_spec.rb | 135 ++++++++++++++++++ .../power_of_attorney/base_serializer_spec.rb | 46 ++++++ .../organization_serializer_spec.rb | 35 +++++ .../representative_serializer_spec.rb | 36 +++++ .../benefits_claims/service_spec.rb | 21 +++ .../power_of_attorney/200_empty_response.yml | 77 ++++++++++ .../power_of_attorney/200_response.yml | 78 ++++++++++ 13 files changed, 560 insertions(+) create mode 100644 modules/representation_management/app/controllers/representation_management/v0/power_of_attorney_controller.rb create mode 100644 modules/representation_management/app/serializers/representation_management/power_of_attorney/base_serializer.rb create mode 100644 modules/representation_management/app/serializers/representation_management/power_of_attorney/organization_serializer.rb create mode 100644 modules/representation_management/app/serializers/representation_management/power_of_attorney/representative_serializer.rb create mode 100644 modules/representation_management/spec/requests/v0/power_of_attorney_request_spec.rb create mode 100644 modules/representation_management/spec/serializers/power_of_attorney/base_serializer_spec.rb create mode 100644 modules/representation_management/spec/serializers/power_of_attorney/organization_serializer_spec.rb create mode 100644 modules/representation_management/spec/serializers/power_of_attorney/representative_serializer_spec.rb create mode 100644 spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney/200_empty_response.yml create mode 100644 spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney/200_response.yml diff --git a/lib/lighthouse/benefits_claims/service.rb b/lib/lighthouse/benefits_claims/service.rb index 3e4313cf0e1..46f200a43ea 100644 --- a/lib/lighthouse/benefits_claims/service.rb +++ b/lib/lighthouse/benefits_claims/service.rb @@ -38,6 +38,14 @@ def get_claim(id, lighthouse_client_id = nil, lighthouse_rsa_key_path = nil, opt raise BenefitsClaims::ServiceException.new(e.response), 'Lighthouse Error' end + def get_power_of_attorney(lighthouse_client_id = nil, lighthouse_rsa_key_path = nil, options = {}) + config.get("#{@icn}/power-of-attorney", lighthouse_client_id, lighthouse_rsa_key_path, options).body + rescue Faraday::TimeoutError + raise BenefitsClaims::ServiceException.new({ status: 504 }), 'Lighthouse Error' + rescue Faraday::ClientError, Faraday::ServerError => e + raise BenefitsClaims::ServiceException.new(e.response), 'Lighthouse Error' + end + def submit5103(user, id, options = {}) params = {} is_dependent = SponsorResolver.dependent?(user) diff --git a/modules/representation_management/app/controllers/representation_management/v0/power_of_attorney_controller.rb b/modules/representation_management/app/controllers/representation_management/v0/power_of_attorney_controller.rb new file mode 100644 index 00000000000..3ec5cfd7986 --- /dev/null +++ b/modules/representation_management/app/controllers/representation_management/v0/power_of_attorney_controller.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'lighthouse/benefits_claims/service' + +module RepresentationManagement + module V0 + class PowerOfAttorneyController < ApplicationController + service_tag 'representation-management' + + def index + @active_poa = lighthouse_service.get_power_of_attorney + + if @active_poa.blank? || record.blank? + render json: { data: {} }, status: :ok + else + render json: record, serializer:, status: :ok + end + end + + private + + def lighthouse_service + BenefitsClaims::Service.new(icn) + end + + def icn + @current_user&.icn + end + + def poa_code + @poa_code ||= @active_poa.dig('data', 'attributes', 'code') + end + + def poa_type + @poa_type ||= @active_poa.dig('data', 'type') + end + + def record + return @record if defined? @record + + @record ||= if poa_type == 'organization' + organization + else + representative + end + end + + def serializer + if poa_type == 'organization' + RepresentationManagement::PowerOfAttorney::OrganizationSerializer + else + RepresentationManagement::PowerOfAttorney::RepresentativeSerializer + end + end + + def organization + Veteran::Service::Organization.find_by(poa: poa_code) + end + + def representative + Veteran::Service::Representative.where('? = ANY(poa_codes)', poa_code).order(created_at: :desc).first + end + end + end +end diff --git a/modules/representation_management/app/serializers/representation_management/power_of_attorney/base_serializer.rb b/modules/representation_management/app/serializers/representation_management/power_of_attorney/base_serializer.rb new file mode 100644 index 00000000000..1b35a8a0605 --- /dev/null +++ b/modules/representation_management/app/serializers/representation_management/power_of_attorney/base_serializer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module RepresentationManagement + module PowerOfAttorney + class BaseSerializer < ActiveModel::Serializer + attribute :address_line1 + attribute :address_line2 + attribute :address_line3 + attribute :address_type + attribute :city + attribute :country_name + attribute :country_code_iso3 + attribute :province + attribute :international_postal_code + attribute :state_code + attribute :zip_code + attribute :zip_suffix + attribute :phone + end + end +end diff --git a/modules/representation_management/app/serializers/representation_management/power_of_attorney/organization_serializer.rb b/modules/representation_management/app/serializers/representation_management/power_of_attorney/organization_serializer.rb new file mode 100644 index 00000000000..c7f97d5b1f4 --- /dev/null +++ b/modules/representation_management/app/serializers/representation_management/power_of_attorney/organization_serializer.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module RepresentationManagement + module PowerOfAttorney + class OrganizationSerializer < BaseSerializer + attribute :type + attribute :name + + def type + 'organization' + end + end + end +end 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 new file mode 100644 index 00000000000..cade1119fed --- /dev/null +++ b/modules/representation_management/app/serializers/representation_management/power_of_attorney/representative_serializer.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module RepresentationManagement + module PowerOfAttorney + class RepresentativeSerializer < BaseSerializer + attribute :type + attribute :name + attribute :email + + def type + 'representative' + end + + def name + object.full_name + end + + def phone + object.phone_number + end + end + end +end diff --git a/modules/representation_management/config/routes.rb b/modules/representation_management/config/routes.rb index b5d3ba5ee0f..241b887ca2e 100644 --- a/modules/representation_management/config/routes.rb +++ b/modules/representation_management/config/routes.rb @@ -3,5 +3,6 @@ RepresentationManagement::Engine.routes.draw do namespace :v0, defaults: { format: 'json' } do resources :flag_accredited_representatives, only: %i[create] + resources :power_of_attorney, only: %i[index] end end diff --git a/modules/representation_management/spec/requests/v0/power_of_attorney_request_spec.rb b/modules/representation_management/spec/requests/v0/power_of_attorney_request_spec.rb new file mode 100644 index 00000000000..80dd5490738 --- /dev/null +++ b/modules/representation_management/spec/requests/v0/power_of_attorney_request_spec.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'RepresentationManagement::V0::PowerOfAttorneyController', type: :request do + let(:index_path) { '/representation_management/v0/power_of_attorney' } + let(:user) { create(:user, :loa3) } + + describe 'index' do + context 'with a signed in user' do + before do + sign_in_as(user) + end + + context 'when an organization is the active poa' do + let(:org_poa) { 'og1' } + let!(:organization) { create(:organization, poa: org_poa) } + + it 'returns the expected organization response' do + lh_response = { + 'data' => { + 'type' => 'organization', + 'attributes' => { + 'code' => org_poa + } + } + } + allow_any_instance_of(BenefitsClaims::Service) + .to receive(:get_power_of_attorney) + .and_return(lh_response) + + get index_path + + response_body = JSON.parse(response.body) + + expect(response).to have_http_status(:ok) + expect(response_body['data']['id']).to eq(org_poa) + end + end + + context 'when a representative is the active poa' do + let(:rep_poa) { 'rp1' } + let(:registration_number) { '12345' } + let!(:representative) do + create(:representative, + representative_id: registration_number, poa_codes: [rep_poa]) + end + + it 'returns the expected representative response' do + lh_response = { + 'data' => { + 'type' => 'individual', + 'attributes' => { + 'code' => rep_poa + } + } + } + allow_any_instance_of(BenefitsClaims::Service) + .to receive(:get_power_of_attorney) + .and_return(lh_response) + + get index_path + + response_body = JSON.parse(response.body) + + expect(response).to have_http_status(:ok) + expect(response_body['data']['id']).to eq(registration_number) + end + end + + context 'when there is no active poa' do + it 'returns the expected empty response' do + lh_response = { + 'data' => {} + } + allow_any_instance_of(BenefitsClaims::Service) + .to receive(:get_power_of_attorney) + .and_return(lh_response) + + get index_path + + response_body = JSON.parse(response.body) + + expect(response).to have_http_status(:ok) + expect(response_body['data']).to eq({}) + end + end + + context 'when the poa record is not found in the database' do + it 'returns the expected empty response' do + lh_response = { + 'data' => { + 'type' => 'organization', + 'attributes' => { + 'code' => 'abc' + } + } + } + allow_any_instance_of(BenefitsClaims::Service) + .to receive(:get_power_of_attorney) + .and_return(lh_response) + + get index_path + + response_body = JSON.parse(response.body) + + expect(response).to have_http_status(:ok) + expect(response_body['data']).to eq({}) + end + end + + context 'when the service encounters an unprocessable entity error' do + it 'returns a 422/unprocessable_entity status' do + allow_any_instance_of(BenefitsClaims::Service) + .to receive(:get_power_of_attorney) + .and_raise(Common::Exceptions::UnprocessableEntity) + + get index_path + + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + context 'without a signed in user' do + describe 'GET #index' do + it 'returns a 401/unauthorized status' do + get index_path + + expect(response).to have_http_status(:unauthorized) + end + end + end + end +end diff --git a/modules/representation_management/spec/serializers/power_of_attorney/base_serializer_spec.rb b/modules/representation_management/spec/serializers/power_of_attorney/base_serializer_spec.rb new file mode 100644 index 00000000000..1daa886c08c --- /dev/null +++ b/modules/representation_management/spec/serializers/power_of_attorney/base_serializer_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'BaseSerializer' do + before do + create(:organization, poa: 'og1') + create(:representative, representative_id: '123', poa_codes: ['rp1']) + end + + def representative + Veteran::Service::Representative.find('123') + end + + def organization + Veteran::Service::Organization.find('og1') + end + + def assert_attributes(attributes) + expect(attributes.keys).to eq(%w[address_line1 + address_line2 + address_line3 + address_type + city + country_name + country_code_iso3 + province + international_postal_code + state_code + zip_code + zip_suffix + phone]) + end + + it 'can serialize a representative' do + result = serialize(representative, serializer_class: RepresentationManagement::PowerOfAttorney::BaseSerializer) + attributes = JSON.parse(result)['data']['attributes'] + assert_attributes(attributes) + end + + it 'can serialize an organization' do + result = serialize(organization, serializer_class: RepresentationManagement::PowerOfAttorney::BaseSerializer) + attributes = JSON.parse(result)['data']['attributes'] + assert_attributes(attributes) + end +end diff --git a/modules/representation_management/spec/serializers/power_of_attorney/organization_serializer_spec.rb b/modules/representation_management/spec/serializers/power_of_attorney/organization_serializer_spec.rb new file mode 100644 index 00000000000..9ec39fbe579 --- /dev/null +++ b/modules/representation_management/spec/serializers/power_of_attorney/organization_serializer_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'OrganizationSerializer' do + before do + create(:organization, poa: 'og1') + end + + def organization + Veteran::Service::Organization.find('og1') + end + + it 'can serialize an organization' do + result = serialize(organization, + serializer_class: RepresentationManagement::PowerOfAttorney::OrganizationSerializer) + attributes = JSON.parse(result)['data']['attributes'] + + expect(attributes.keys).to eq(%w[address_line1 + address_line2 + address_line3 + address_type + city + country_name + country_code_iso3 + province + international_postal_code + state_code + zip_code + zip_suffix + phone + type + name]) + end +end 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 new file mode 100644 index 00000000000..c39ea43ec7e --- /dev/null +++ b/modules/representation_management/spec/serializers/power_of_attorney/representative_serializer_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'RepresentativeSerializer' do + before do + create(:representative, representative_id: '123', poa_codes: ['rp1']) + end + + def representative + Veteran::Service::Representative.find('123') + end + + it 'can serialize a representative' do + result = serialize(representative, + serializer_class: RepresentationManagement::PowerOfAttorney::RepresentativeSerializer) + attributes = JSON.parse(result)['data']['attributes'] + + expect(attributes.keys).to eq(%w[address_line1 + address_line2 + address_line3 + address_type + city + country_name + country_code_iso3 + province + international_postal_code + state_code + zip_code + zip_suffix + phone + type + name + email]) + end +end diff --git a/spec/lib/lighthouse/benefits_claims/service_spec.rb b/spec/lib/lighthouse/benefits_claims/service_spec.rb index 27035f97ea0..d5ecd4fe022 100644 --- a/spec/lib/lighthouse/benefits_claims/service_spec.rb +++ b/spec/lib/lighthouse/benefits_claims/service_spec.rb @@ -56,6 +56,27 @@ end end + describe "when requesting a user's power of attorney" do + context 'when the user has an active power of attorney' do + it 'retrieves the power of attorney from the Lighthouse API' do + VCR.use_cassette('lighthouse/benefits_claims/power_of_attorney/200_response') do + response = @service.get_power_of_attorney + expect(response['data']['type']).to eq('individual') + expect(response['data']['attributes']['code']).to eq('067') + end + end + end + + context 'when the user does not have an active power of attorney' do + it 'retrieves the power of attorney from the Lighthouse API' do + VCR.use_cassette('lighthouse/benefits_claims/power_of_attorney/200_empty_response') do + response = @service.get_power_of_attorney + expect(response['data']).to eq({}) + end + end + end + end + describe 'when posting a form526' do it 'when given a full request body, posts to the Lighthouse API' do VCR.use_cassette('lighthouse/benefits_claims/submit526/200_response') do diff --git a/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney/200_empty_response.yml b/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney/200_empty_response.yml new file mode 100644 index 00000000000..d7878dd339e --- /dev/null +++ b/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney/200_empty_response.yml @@ -0,0 +1,77 @@ +--- +http_interactions: +- request: + method: get + uri: https://sandbox-api.va.gov/services/claims/v2/veterans/123498767V234859/power-of-attorney + 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, 04 Apr 2024 17:45:59 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '11' + Connection: + - keep-alive + Ratelimit-Remaining: + - '118' + Ratelimit-Reset: + - '3' + X-Ratelimit-Limit-Minute: + - '120' + X-Ratelimit-Remaining-Minute: + - '118' + Ratelimit-Limit: + - '120' + Etag: + - W/"7fb9d166d1a15bce0b9f085f3818946f" + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + - SAMEORIGIN + X-Git-Sha: + - 77911592048236d8eccb0f39f5e9f758564e282b + X-Github-Repository: + - https://github.com/department-of-veterans-affairs/vets-api + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 8dd9e3bb-6159-4f92-b92d-7dbe791547e0 + X-Runtime: + - '2.017096' + 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 + body: + encoding: UTF-8 + string: '{"data":{}}' + recorded_at: Thu, 04 Apr 2024 17:46:00 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney/200_response.yml b/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney/200_response.yml new file mode 100644 index 00000000000..bcf3bbe002d --- /dev/null +++ b/spec/support/vcr_cassettes/lighthouse/benefits_claims/power_of_attorney/200_response.yml @@ -0,0 +1,78 @@ +--- +http_interactions: +- request: + method: get + uri: https://sandbox-api.va.gov/services/claims/v2/veterans/123498767V234859/power-of-attorney + 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: + - Tue, 02 Apr 2024 14:30:58 GMT + Content-Type: + - application/json; charset=utf-8 + Connection: + - keep-alive + X-Ratelimit-Remaining-Minute: + - '119' + X-Ratelimit-Limit-Minute: + - '120' + Ratelimit-Remaining: + - '119' + Ratelimit-Limit: + - '120' + Ratelimit-Reset: + - '10' + Etag: + - W/"f674f2056c77718a448e94dc8a1ed556" + Referrer-Policy: + - strict-origin-when-cross-origin + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + - SAMEORIGIN + X-Git-Sha: + - 11de6c4d9dc0e22795b79e94395598e9c7e0fc3c + X-Github-Repository: + - https://github.com/department-of-veterans-affairs/vets-api + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - f7395f08-c7c7-4726-85e4-602c6ecf40fe + X-Runtime: + - '8.308072' + 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":{"type":"individual","attributes":{"code":"067","name":"Tamara + Ellis","phoneNumber":null}}}' + recorded_at: Tue, 02 Apr 2024 14:30:58 GMT +recorded_with: VCR 6.2.0 From 5f14f92257f6baddb5fa17189a28e5197b1d5f32 Mon Sep 17 00:00:00 2001 From: evansmith Date: Mon, 8 Apr 2024 11:12:11 -0400 Subject: [PATCH 2/5] add new keys to attachment keys in anticipation of v2 (#16228) * add new keys to attachment keys in anticipation of v2 * add codeowners entry for burial.rb --------- Co-authored-by: Thomas Blackwell --- .github/CODEOWNERS | 1 + app/models/saved_claim/burial.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f9481452e5f..8da827ed0ea 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -290,6 +290,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/Benefits-Team-1 @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group app/models/session.rb @department-of-veterans-affairs/octo-identity +app/models/saved_claim/burial.rb @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/pensions @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group app/models/saved_claim/pension.rb @department-of-veterans-affairs/pensions @department-of-veterans-affairs/backend-review-group app/models/saved_claim/veteran_readiness_employment_claim.rb @department-of-veterans-affairs/benefits-non-disability @department-of-veterans-affairs/va-api-engineers @department-of-veterans-affairs/backend-review-group app/models/sign_in @department-of-veterans-affairs/octo-identity diff --git a/app/models/saved_claim/burial.rb b/app/models/saved_claim/burial.rb index 5bf7b4b7de8..fd9b1f858f2 100644 --- a/app/models/saved_claim/burial.rb +++ b/app/models/saved_claim/burial.rb @@ -32,7 +32,7 @@ def regional_office end def attachment_keys - %i[transportationReceipts deathCertificate].freeze + %i[transportationReceipts deathCertificate militarySeparationDocuments additionalEvidence].freeze end def email From d1eb7f411de2b51a41d9c3a093e2700b40e34707 Mon Sep 17 00:00:00 2001 From: Richard Davis Date: Mon, 8 Apr 2024 11:12:38 -0400 Subject: [PATCH 3/5] Adds debugging info to /v0/profile/contacts endpoint (#16174) --- app/swagger/swagger/schemas/contacts.rb | 8 ++- lib/va_profile/models/associated_person.rb | 12 +++- .../profile/v3/health_benefit_bio_response.rb | 30 +++++---- lib/va_profile/profile/v3/service.rb | 6 +- .../v0/profile/contacts_controller_spec.rb | 2 +- .../models/associated_person_spec.rb | 5 +- .../v3/health_benefit_bio_response_spec.rb | 27 ++++---- .../lib/va_profile/profile/v3/service_spec.rb | 43 ++++++++++-- .../va_profile/contacts_request_spec.rb | 66 ++++++++++++++----- 9 files changed, 144 insertions(+), 55 deletions(-) diff --git a/app/swagger/swagger/schemas/contacts.rb b/app/swagger/swagger/schemas/contacts.rb index d19585b9085..e6a01bf6276 100644 --- a/app/swagger/swagger/schemas/contacts.rb +++ b/app/swagger/swagger/schemas/contacts.rb @@ -10,7 +10,7 @@ class Contacts key :required, [:data] property :data, type: :array do items do - property :id, type: :string, example: 'dbbf9a58-41e5-40c0-bdb5-fc1407aa1f05' + property :id, type: :string property :type, type: :string property :attributes do key :$ref, :Contact @@ -21,7 +21,11 @@ class Contacts swagger_schema :Contact do key :required, %i[contact_type given_name family_name primary_phone] - property :contact_type, type: :string, enum: VAProfile::Models::AssociatedPerson::CONTACT_TYPES + property( + :contact_type, + type: :string, + enum: VAProfile::Models::AssociatedPerson::PERSONAL_HEALTH_CARE_CONTACT_TYPES + ) property :given_name, type: %i[string null] property :family_name, type: %i[string null] property :relationship, type: %i[string null] diff --git a/lib/va_profile/models/associated_person.rb b/lib/va_profile/models/associated_person.rb index 9f9f72c25bc..c100e0e1d33 100644 --- a/lib/va_profile/models/associated_person.rb +++ b/lib/va_profile/models/associated_person.rb @@ -10,19 +10,27 @@ class AssociatedPerson < Base OTHER_EMERGENCY_CONTACT = 'Other emergency contact' PRIMARY_NEXT_OF_KIN = 'Primary Next of Kin' OTHER_NEXT_OF_KIN = 'Other Next of Kin' + DESIGNEE = 'Designee' + POWER_OF_ATTORNEY = 'Power of Attorney' - CONTACT_TYPES = [ + PERSONAL_HEALTH_CARE_CONTACT_TYPES = [ EMERGENCY_CONTACT, OTHER_EMERGENCY_CONTACT, PRIMARY_NEXT_OF_KIN, OTHER_NEXT_OF_KIN ].freeze + CONTACT_TYPES = [ + *PERSONAL_HEALTH_CARE_CONTACT_TYPES, + DESIGNEE, + POWER_OF_ATTORNEY + ].freeze + attribute :contact_type, String attribute :given_name, Common::TitlecaseString attribute :middle_name, Common::TitlecaseString attribute :family_name, Common::TitlecaseString - attribute :relationship, Common::TitlecaseString + attribute :relationship, String attribute :address_line1, String attribute :address_line2, String attribute :address_line3, String diff --git a/lib/va_profile/profile/v3/health_benefit_bio_response.rb b/lib/va_profile/profile/v3/health_benefit_bio_response.rb index 1dde3fb9f89..1b333021400 100644 --- a/lib/va_profile/profile/v3/health_benefit_bio_response.rb +++ b/lib/va_profile/profile/v3/health_benefit_bio_response.rb @@ -8,33 +8,39 @@ module VAProfile module Profile module V3 class HealthBenefitBioResponse < VAProfile::Response - attr_reader :body - attribute :contacts, Array[VAProfile::Models::AssociatedPerson] attribute :messages, Array[VAProfile::Models::Message] + attribute :va_profile_tx_audit_id, String def initialize(response) - @body = response.body + body = response.body contacts = body.dig('profile', 'health_benefit', 'associated_persons') &.select { |p| valid_contact_types.include?(p['contact_type']) } &.sort_by { |p| valid_contact_types.index(p['contact_type']) } messages = body['messages'] - super(response.status, { contacts:, messages: }) + va_profile_tx_audit_id = response.response_headers['vaprofiletxauditid'] + super(response.status, { contacts:, messages:, va_profile_tx_audit_id: }) end - def metadata - { status:, messages: } + def debug_data + { + status:, + message:, + va_profile_tx_audit_id: + } end private def valid_contact_types - [ - VAProfile::Models::AssociatedPerson::EMERGENCY_CONTACT, - VAProfile::Models::AssociatedPerson::OTHER_EMERGENCY_CONTACT, - VAProfile::Models::AssociatedPerson::PRIMARY_NEXT_OF_KIN, - VAProfile::Models::AssociatedPerson::OTHER_NEXT_OF_KIN - ] + VAProfile::Models::AssociatedPerson::PERSONAL_HEALTH_CARE_CONTACT_TYPES + end + + def message + m = messages&.first + return '' unless m + + "#{m.code} #{m.key} #{m.text}" end end end diff --git a/lib/va_profile/profile/v3/service.rb b/lib/va_profile/profile/v3/service.rb index 8c26e61e49f..8b06ccc5f59 100644 --- a/lib/va_profile/profile/v3/service.rb +++ b/lib/va_profile/profile/v3/service.rb @@ -26,8 +26,10 @@ def initialize(user) def get_health_benefit_bio oid = MPI::Constants::VA_ROOT_OID path = "#{oid}/#{ERB::Util.url_encode(icn_with_aaid)}" - response = perform(:post, path, { bios: [{ bioPath: 'healthBenefit' }] }) - VAProfile::Profile::V3::HealthBenefitBioResponse.new(response) + service_response = perform(:post, path, { bios: [{ bioPath: 'healthBenefit' }] }) + response = VAProfile::Profile::V3::HealthBenefitBioResponse.new(service_response) + Sentry.set_extras(response.debug_data) unless response.ok? + response end def get_military_info diff --git a/spec/controllers/v0/profile/contacts_controller_spec.rb b/spec/controllers/v0/profile/contacts_controller_spec.rb index 2ddf67c1864..62131d4d71d 100644 --- a/spec/controllers/v0/profile/contacts_controller_spec.rb +++ b/spec/controllers/v0/profile/contacts_controller_spec.rb @@ -44,7 +44,7 @@ end context 'feature disabled' do - it 'returns an unauthorized status code' do + it 'returns a not found status code' do Flipper.disable(:profile_contacts) sign_in_as user expect(subject).to have_http_status(:not_found) diff --git a/spec/lib/va_profile/models/associated_person_spec.rb b/spec/lib/va_profile/models/associated_person_spec.rb index 8aedcb5ac90..42ee2701a06 100644 --- a/spec/lib/va_profile/models/associated_person_spec.rb +++ b/spec/lib/va_profile/models/associated_person_spec.rb @@ -35,15 +35,12 @@ it 'titlecases family_name' do expect(subject.family_name).to eq('Williams') end - - it 'titlecases relationship' do - expect(subject.relationship).to eq('Unrelated Friend') - end end context 'Virtus::Attribute, String type attributes' do %i[ contact_type + relationship address_line1 address_line2 address_line3 diff --git a/spec/lib/va_profile/profile/v3/health_benefit_bio_response_spec.rb b/spec/lib/va_profile/profile/v3/health_benefit_bio_response_spec.rb index 70f51f3ed61..8cc61adcadb 100644 --- a/spec/lib/va_profile/profile/v3/health_benefit_bio_response_spec.rb +++ b/spec/lib/va_profile/profile/v3/health_benefit_bio_response_spec.rb @@ -7,17 +7,22 @@ subject { described_class.new(response) } let(:response) do - double('Faraday::Response', - status: 200, - body: { - 'profile' => { - 'health_benefit' => { - 'associated_persons' => [{ - 'contact_type' => contact_type - }] - } - } - }) + double( + 'Faraday::Response', + status: 200, + body: { + 'profile' => { + 'health_benefit' => { + 'associated_persons' => [{ + 'contact_type' => contact_type + }] + } + } + }, + response_headers: { + 'vaprofiletxauditid' => 'abc123' + } + ) end describe 'Emergency contact' do diff --git a/spec/lib/va_profile/profile/v3/service_spec.rb b/spec/lib/va_profile/profile/v3/service_spec.rb index da5ec7b9b89..cd37ee8998d 100644 --- a/spec/lib/va_profile/profile/v3/service_spec.rb +++ b/spec/lib/va_profile/profile/v3/service_spec.rb @@ -55,6 +55,19 @@ describe '#get_health_benefit_bio' do let(:user) { build(:user, :loa3, idme_uuid:) } + let(:cassette_filename) { "spec/support/vcr_cassettes/#{cassette}.yml" } + let(:cassette_data) { YAML.load_file(cassette_filename) } + let(:va_profile_tx_audit_id) do + cassette_data['http_interactions'][0]['response']['headers']['Vaprofiletxauditid'][0] + end + let(:debug_data) do + { + status:, + message:, + va_profile_tx_audit_id: + } + end + around do |ex| VCR.use_cassette(cassette) { ex.run } end @@ -68,19 +81,21 @@ expect(response.status).to eq(200) expect(response.contacts.size).to eq(4) types = response.contacts.map(&:contact_type) - valid_contact_types = [ - VAProfile::Models::AssociatedPerson::EMERGENCY_CONTACT, - VAProfile::Models::AssociatedPerson::OTHER_EMERGENCY_CONTACT, - VAProfile::Models::AssociatedPerson::PRIMARY_NEXT_OF_KIN, - VAProfile::Models::AssociatedPerson::OTHER_NEXT_OF_KIN - ] + valid_contact_types = VAProfile::Models::AssociatedPerson::PERSONAL_HEALTH_CARE_CONTACT_TYPES expect(types).to match_array(valid_contact_types) end + + it 'does not call Sentry.set_extras' do + expect(Sentry).not_to receive(:set_extras) + subject.get_health_benefit_bio + end end context '404 response' do let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' } let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_404' } + let(:status) { 404 } + let(:message) { 'MVI201 MviNotFound The person with the identifier requested was not found in MVI.' } it 'includes messages received from the api' do response = subject.get_health_benefit_bio @@ -88,11 +103,22 @@ expect(response.contacts.size).to eq(0) expect(response.messages.size).to eq(1) end + + it 'calls Sentry.set_extras' do + expect(Sentry).to receive(:set_extras).once.with(debug_data) + subject.get_health_benefit_bio + end end context '500 response' do let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' } let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_500' } + let(:status) { 500 } + let(:message) do + result = 'MVI203 MviResponseError MVI returned acknowledgement error code ' + result += 'AE with error detail: More Than One Active Correlation Exists' + result + end it 'includes messages recieved from the api' do response = subject.get_health_benefit_bio @@ -100,6 +126,11 @@ expect(response.contacts.size).to eq(0) expect(response.messages.size).to eq(1) end + + it 'calls Sentry.set_extras' do + expect(Sentry).to receive(:set_extras).once.with(debug_data) + subject.get_health_benefit_bio + end end context 'api timeout' do diff --git a/spec/requests/va_profile/contacts_request_spec.rb b/spec/requests/va_profile/contacts_request_spec.rb index b44363b3ddd..4f8fef43a7a 100644 --- a/spec/requests/va_profile/contacts_request_spec.rb +++ b/spec/requests/va_profile/contacts_request_spec.rb @@ -7,29 +7,65 @@ let(:user) { build(:user, :loa3, idme_uuid:) } let(:resource) { JSON.parse(response.body) } - describe 'GET /v0/profile/contacts -> 200' do - let(:idme_uuid) { 'dd681e7d6dea41ad8b80f8d39284ef29' } + around do |ex| + VCR.use_cassette(cassette) { ex.run } + end + + describe 'GET /v0/profile/contacts' do + context '200 response' do + let(:idme_uuid) { 'dd681e7d6dea41ad8b80f8d39284ef29' } + let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_200' } - it 'responds with contacts' do - sign_in_as(user) - VCR.use_cassette('va_profile/profile/v3/health_benefit_bio_200') do + it 'responds with contacts' do + sign_in_as(user) get '/v0/profile/contacts' + expect(response).to have_http_status(:ok) + expect(response).to match_response_schema('contacts') + expect(resource['data'].size).to eq(4) + end + end + + context '401 response' do + let(:idme_uuid) { 'dd681e7d6dea41ad8b80f8d39284ef29' } + let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_200' } + + it 'responds with 401 status' do + get '/v0/profile/contacts' + expect(response).to have_http_status(:unauthorized) + end + end + + context '403 response' do + let(:user) { build(:user, :loa1) } + let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_200' } + + it 'responds with 403 status' do + sign_in_as(user) + get '/v0/profile/contacts' + expect(response).to have_http_status(:forbidden) + end + end + + context '404 response' do + let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' } + let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_404' } + + it 'responds with 404 status' do + sign_in_as(user) + get '/v0/profile/contacts' + expect(response).to have_http_status(:not_found) end - expect(response).to have_http_status(:ok) - expect(response).to match_response_schema('contacts') - expect(resource['data'].size).to eq(4) end - end - describe 'GET /v0/profile/contacts -> 404' do - let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' } + context '500 response' do + let(:idme_uuid) { '88f572d4-91af-46ef-a393-cba6c351e252' } + let(:cassette) { 'va_profile/profile/v3/health_benefit_bio_500' } - it 'responds with 404 status' do - sign_in_as(user) - VCR.use_cassette('va_profile/profile/v3/health_benefit_bio_404') do + it 'responds with 500 status' do + sign_in_as(user) get '/v0/profile/contacts' + expect(response).to have_http_status(:internal_server_error) end - expect(response).to have_http_status(:not_found) end end end From e97c23b95435159d775f3e88be14303627640129 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:01:16 -0600 Subject: [PATCH 4/5] Bump rubocop from 1.62.1 to 1.63.0 (#16233) Bumps [rubocop](https://github.com/rubocop/rubocop) from 1.62.1 to 1.63.0. - [Release notes](https://github.com/rubocop/rubocop/releases) - [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](https://github.com/rubocop/rubocop/compare/v1.62.1...v1.63.0) --- updated-dependencies: - dependency-name: rubocop dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 95f1b0042fa..c2f6771ab7f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -890,7 +890,7 @@ GEM rswag-ui (2.13.0) actionpack (>= 3.1, < 7.2) railties (>= 3.1, < 7.2) - rubocop (1.62.1) + rubocop (1.63.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) From 57d14caea56dac652bd620f8d61f80d15e2750f7 Mon Sep 17 00:00:00 2001 From: Micah Frazier Date: Mon, 8 Apr 2024 10:36:46 -0600 Subject: [PATCH 5/5] Ndbex/80091 zip code bug (#16224) * level set * level set with master * removing yarn.lock * Fix for foreign zipcode bug for Burials * Fix for zipcode issue setting the zipcode as being foreign even if it was not * Rubocop * Rubocop * Fix for logic --------- Co-authored-by: Thomas Blackwell --- app/sidekiq/lighthouse/submit_benefits_intake_claim.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb b/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb index ff98683f62d..34553145270 100644 --- a/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb +++ b/app/sidekiq/lighthouse/submit_benefits_intake_claim.rb @@ -77,13 +77,13 @@ def generate_metadata 'veteranFirstName' => veteran_full_name['first'], 'veteranLastName' => veteran_full_name['last'], 'fileNumber' => form['vaFileNumber'] || form['veteranSocialSecurityNumber'], - 'zipCode' => address['country'] == 'USA' ? address['postalCode'] : FOREIGN_POSTALCODE, + 'zipCode' => address['postalCode'], 'source' => "#{@claim.class} va.gov", 'docType' => @claim.form_id, 'businessLine' => @claim.business_line } - SimpleFormsApiSubmission::MetadataValidator.validate(metadata) + SimpleFormsApiSubmission::MetadataValidator.validate(metadata, zip_code_is_us_based: check_zipcode(address)) end # rubocop:disable Metrics/MethodLength @@ -149,5 +149,9 @@ def cleanup_file_paths Common::FileHelpers.delete_file_if_exists(@pdf_path) if @pdf_path @attachment_paths&.each { |p| Common::FileHelpers.delete_file_if_exists(p) } end + + def check_zipcode(address) + address['country'].upcase.in?(%w[USA US]) + end end end