From e459d58e5e36b2612a2bdb0491717785449f4613 Mon Sep 17 00:00:00 2001 From: Devin McCurdy <41960449+devin-mccurdy@users.noreply.github.com> Date: Wed, 18 Dec 2024 09:53:41 -0500 Subject: [PATCH] Patient relationship failures (#19887) --- .../vaos/v2/relationships_controller.rb | 5 +- .../services/vaos/v2/relationships_service.rb | 40 +++++- .../request/v2/relationships_request_spec.rb | 39 ----- .../requests/vaos/v2/relationships_spec.rb | 70 +++++++++ .../v2/relationships/get_relationships.yml | 3 +- .../get_relationships_partial_error.yml | 136 ++++++++++++++++++ 6 files changed, 248 insertions(+), 45 deletions(-) delete mode 100644 modules/vaos/spec/request/v2/relationships_request_spec.rb create mode 100644 modules/vaos/spec/requests/vaos/v2/relationships_spec.rb create mode 100644 spec/support/vcr_cassettes/vaos/v2/relationships/get_relationships_partial_error.yml diff --git a/modules/vaos/app/controllers/vaos/v2/relationships_controller.rb b/modules/vaos/app/controllers/vaos/v2/relationships_controller.rb index e9021f6dd60..ff1eab78de2 100644 --- a/modules/vaos/app/controllers/vaos/v2/relationships_controller.rb +++ b/modules/vaos/app/controllers/vaos/v2/relationships_controller.rb @@ -9,10 +9,7 @@ def index relationships_params[:facility_id] ) - serialized = VAOS::V2::VAOSSerializer.new.serialize(response, 'relationship') - serialized.each { |relationship| relationship.delete(:id) } - - render json: { data: serialized } + render json: response end private diff --git a/modules/vaos/app/services/vaos/v2/relationships_service.rb b/modules/vaos/app/services/vaos/v2/relationships_service.rb index 5d1ff717081..51a86e63ced 100644 --- a/modules/vaos/app/services/vaos/v2/relationships_service.rb +++ b/modules/vaos/app/services/vaos/v2/relationships_service.rb @@ -15,10 +15,48 @@ def get_patient_relationships(clinic_service_id, facility_id) } response = perform(:get, "/vpg/v1/patients/#{user.icn}/relationships", params, headers) + relationships = response[:body][:data][:relationships].map { |relationship| OpenStruct.new(relationship) } - response[:body][:data][:relationships].map { |relationship| OpenStruct.new(relationship) } + data = VAOS::V2::VAOSSerializer.new.serialize(relationships, 'relationship') + data.each { |relationship| relationship.delete(:id) } + + { data: data, meta: partial_errors(response) } end end + + private + + def partial_errors(response) + return { failures: [] } if response.body[:failures].blank? + + log_partial_errors(response) + + { + failures: response.body[:failures] + } + end + + # Logs partial errors from a response. + # + # @param response [Faraday::Env] The response object containing the status and body. + # + # @return [nil] + # + def log_partial_errors(response) + return unless response.status == 200 + + failures_dup = response.body[:failures].deep_dup + failures_dup.each do |failure| + detail = failure[:detail] + failure[:detail] = VAOS::Anonymizers.anonymize_icns(detail) if detail.present? + end + + log_message_to_sentry( + 'VAOS::V2::RelationshipsService#get_patient_relationships has response errors.', + :info, + failures: failures_dup.to_json + ) + end end end end diff --git a/modules/vaos/spec/request/v2/relationships_request_spec.rb b/modules/vaos/spec/request/v2/relationships_request_spec.rb deleted file mode 100644 index 160c19b0702..00000000000 --- a/modules/vaos/spec/request/v2/relationships_request_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe 'relationships', :skip_mvi, type: :request do - before do - sign_in_as(current_user) - allow_any_instance_of(VAOS::UserService).to receive(:session).and_return('stubbed_token') - allow(Flipper).to receive(:enabled?).and_return(true) - end - - let(:inflection_header) { { 'X-Key-Inflection' => 'camel' } } - - context 'loa3 user' do - let(:current_user) { build(:user, :vaos) } - - describe 'GET relationships' do - let(:params) { { clinical_service_id: 'primaryCare', facility_id: '100' } } - - context 'patient relationships' do - it 'successfully returns patient relationships' do - VCR.use_cassette('vaos/v2/relationships/get_relationships', - match_requests_on: %i[method path query]) do - get '/vaos/v2/relationships?clinicalServiceId=primaryCare&facilityId=100', params:, - headers: inflection_header - expect(response).to have_http_status(:ok) - - relationships = JSON.parse(response.body)['data'] - expect(relationships).not_to be_nil - expect(relationships.length).to eq(4) - relationships.each do |relationship| - expect(relationship['type']).to eq('relationship') - end - end - end - end - end - end -end diff --git a/modules/vaos/spec/requests/vaos/v2/relationships_spec.rb b/modules/vaos/spec/requests/vaos/v2/relationships_spec.rb new file mode 100644 index 00000000000..743417082a8 --- /dev/null +++ b/modules/vaos/spec/requests/vaos/v2/relationships_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'relationships', :skip_mvi, type: :request do + before do + sign_in_as(current_user) + allow_any_instance_of(VAOS::UserService).to receive(:session).and_return('stubbed_token') + allow(Flipper).to receive(:enabled?).and_return(true) + end + + let(:inflection_header) { { 'X-Key-Inflection' => 'camel' } } + + context 'loa3 user' do + let(:current_user) { build(:user, :vaos) } + + describe 'GET relationships' do + let(:params) { { clinical_service_id: 'primaryCare', facility_id: '100' } } + + context 'patient relationships' do + it 'successfully returns patient relationships' do + VCR.use_cassette('vaos/v2/relationships/get_relationships', + match_requests_on: %i[method path query]) do + get '/vaos/v2/relationships?clinicalServiceId=primaryCare&facilityId=100', params:, + headers: inflection_header + expect(response).to have_http_status(:ok) + + relationships = JSON.parse(response.body)['data'] + expect(relationships).not_to be_nil + expect(relationships.length).to eq(4) + relationships.each do |relationship| + expect(relationship['type']).to eq('relationship') + end + + meta = JSON.parse(response.body)['meta'] + expect(meta['failures']).to eq([]) + end + end + + it 'returns patient relationships containing partial errors' do + VCR.use_cassette('vaos/v2/relationships/get_relationships_partial_error', + match_requests_on: %i[method path query]) do + allow(Rails.logger).to receive(:info).at_least(:once) + get '/vaos/v2/relationships?clinicalServiceId=primaryCare&facilityId=100', params:, + headers: inflection_header + expect(response).to have_http_status(:ok) + + relationships = JSON.parse(response.body)['data'] + expect(relationships).not_to be_nil + expect(relationships.length).to eq(4) + relationships.each do |relationship| + expect(relationship['type']).to eq('relationship') + end + + meta = JSON.parse(response.body)['meta'] + expect(meta['failures'].length).to eq(1) + expect(meta['failures'][0]['system']).to eq('CFA') + expect(meta['failures'][0]['id']).to eq('id-string') + expect(meta['failures'][0]['status']).to eq('status-string') + expect(meta['failures'][0]['detail']).to eq('detail-string') + expect(meta['failures'][0]['traceId']).to eq('traceId-string') + expect(meta['failures'][0]['code']).to eq(0) + + expect(Rails.logger).to have_received(:info).at_least(:once) + end + end + end + end + end +end diff --git a/spec/support/vcr_cassettes/vaos/v2/relationships/get_relationships.yml b/spec/support/vcr_cassettes/vaos/v2/relationships/get_relationships.yml index eb646f5ab0c..8b1231c136c 100644 --- a/spec/support/vcr_cassettes/vaos/v2/relationships/get_relationships.yml +++ b/spec/support/vcr_cassettes/vaos/v2/relationships/get_relationships.yml @@ -119,7 +119,8 @@ http_interactions: "lastSeen": "2024-08-22T21:20:52.661Z" } ] - } + }, + "failures": [] } recorded_at: Fri, 23 Aug 2024 14:46:55 GMT recorded_with: VCR 6.2.0 diff --git a/spec/support/vcr_cassettes/vaos/v2/relationships/get_relationships_partial_error.yml b/spec/support/vcr_cassettes/vaos/v2/relationships/get_relationships_partial_error.yml new file mode 100644 index 00000000000..374ba0de898 --- /dev/null +++ b/spec/support/vcr_cassettes/vaos/v2/relationships/get_relationships_partial_error.yml @@ -0,0 +1,136 @@ +--- +http_interactions: + - request: + method: get + uri: https://veteran.apps.va.gov/vpg/v1/patients/1012845331V153043/relationships?clinicalService=primaryCare&location=100 + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Vets.gov Agent + Referer: + - https://review-instance.va.gov + X-Vamf-Jwt: + - stubbed_token + X-Request-Id: + - 06054f2f-fec7-4ee1-be71-6d9e381f063a + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 23 Aug 2024 14:46:54 GMT + Content-Type: + - application/json + Access-Control-Allow-Headers: + - x-vamf-jwt + Access-Control-Allow-Origin: + - "*" + Access-Control-Allow-Methods: + - GET,OPTIONS + Access-Control-Max-Age: + - '3600' + body: + encoding: UTF-8 + string: |- + { + "data": { + "patientIcn": "1012781163V209546", + "relationships": [ + { + "provider": { + "cernerId": "Practitioner/1", + "name": "House, Gregory, M.D." + }, + "location": { + "vhaFacilityId": "653", + "name": "653 ROS OR VA" + }, + "clinic": null, + "serviceType": null, + "lastSeen": "2024-02-22T21:20:52.661Z" + }, + { + "provider": { + "cernerId": "Practitioner/1", + "name": "House, Gregory, M.D." + }, + "location": { + "vhaFacilityId": "653", + "name": "653 ROS OR VA" + }, + "clinic": null, + "serviceType": { + "coding": { + "system": "http://veteran.apps.va.gov/terminologies/fhir/CodeSystem/vats-service-type", + "code": "primaryCare", + "display": "Primary Care" + }, + "text": "Primary Care" + }, + "lastSeen": "2024-08-22T21:20:52.661Z" + }, + { + "provider": { + "cernerId": "Practitioner/1", + "name": "House, Gregory, M.D." + }, + "location": { + "vhaFacilityId": "653GB", + "name": "653GB BRK OR VA" + }, + "clinic": "null", + "serviceType": { + "coding": { + "system": "http://veteran.apps.va.gov/terminologies/fhir/CodeSystem/vats-service-type", + "code": "optometry", + "display": "Optometry" + }, + "text": "Optometry" + }, + "lastSeen": "2023-08-22T21:20:52.661Z" + }, + { + "provider": { + "cernerId": "Practitioner/2", + "name": "Cuddy, Lisa, M.D." + }, + "location": { + "vhaFacilityId": "653GA", + "name": "653GA NBD OR VA" + }, + "clinic": null, + "serviceType": { + "coding": { + "system": "http://veteran.apps.va.gov/terminologies/fhir/CodeSystem/vats-service-type", + "code": "audiology", + "display": "Audiology" + }, + "text": "Audiology" + }, + "lastSeen": "2024-08-22T21:20:52.661Z" + } + ] + }, + "failures": [ + { + "system":"CFA", + "id":"id-string", + "status":"status-string", + "code":0, + "traceId":"traceId-string", + "message":"Failed to fetch relationships from CFA", + "detail":"detail-string" + } + ] + } + recorded_at: Fri, 23 Aug 2024 14:46:55 GMT +recorded_with: VCR 6.2.0