Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue# 98709 & 98710 POA Request List and Detail endpoints #19967

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,16 @@
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: '[email protected]',
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add .limit(100) just in case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we want to includes the rest of the object graph to not have N+1 query like the original proof of concept spec shows. So maybe this becomes includes(resolution: :resolving)?

render json: PowerOfAttorneyRequestSerializer.new(poa_requests).serializable_hash, status: :ok
end

def show
render json: POA_REQUEST_ITEM_MOCK_DATA
poa_request = PowerOfAttorneyRequest.includes(:resolution).find(params[:id])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we want to includes the rest of the object graph to not have N+1 query like the original proof of concept spec shows. So maybe this becomes includes(resolution: :resolving)?

render json: PowerOfAttorneyRequestSerializer.new(poa_request).serializable_hash, status: :ok
rescue ActiveRecord::RecordNotFound
render json: { error: 'Record not found' }, status: :not_found
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module AccreditedRepresentativePortal
class PowerOfAttorneyRequestResolutionSerializer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow the library's guide for polymorphic relationship instead.

include JSONAPI::Serializer

attribute :id

attribute :type do |object|
object.resolving_type.demodulize.underscore.split('_').last
end
Comment on lines +9 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have the polymorphic variants, each of them can hardcode this rather than manipulate the name of the Ruby class. In the end we should get: "type": "decision" and "type": "expiration".


attribute :decision_type, if: proc { |obj| obj.resolving.respond_to?(:type) } do |object|
object.resolving.type
end

attribute :reason
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reason will only need to be serialized on a declination in the decision serializer variant.


attribute :creator_id, if: proc { |obj| obj.resolving.respond_to?(:creator_id) } do |object|
object.resolving.creator_id
end

attribute :created_at do |object|
object.created_at.iso8601
end
Comment on lines +23 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How was this serializing without the custom attribute?

end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module AccreditedRepresentativePortal
class PowerOfAttorneyRequestSerializer
include JSONAPI::Serializer

attributes :id, :claimant_id

attribute :created_at do |object|
object.created_at.iso8601
end
Comment on lines +9 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How was this getting serialized without the custom attribute?


attribute :resolution do |object|
next nil if object.resolution.blank?

AccreditedRepresentativePortal::PowerOfAttorneyRequestResolutionSerializer.new(
object.resolution
).serializable_hash[:data][:attributes]
end
Comment on lines +13 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use a relationship serializer? In fact it looks like we want to follow their example here for polymorphism:

class MovieSerializer
  include JSONAPI::Serializer

  has_many :actors, serializer: Proc.new do |record, params|
    if record.comedian?
      ComedianSerializer
    elsif params[:use_drama_serializer]
      DramaSerializer
    else
      ActorSerializer
    end
  end
end

Copy link
Contributor Author

@ojbucao ojbucao Dec 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't control the structure that way. The result isn't flat like how we want it to be (they get placed under "relationships"). And there doesn't seem to be a way to override the default serialization when using relationships.

end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@
id { Faker::Internet.uuid }
association :creator, factory: :user_account
type { 'Approval' }

trait :declination do
type { 'Declination' }
end

trait :approval do
type { 'Approval' }
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
association :claimant, factory: :user_account
id { Faker::Internet.uuid }
created_at { Time.current }

trait :with_resolution do
resolution { create(:power_of_attorney_request_resolution, :with_decision) }
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,30 @@
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' }
association :power_of_attorney_request
resolving_type { 'AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration' }
reason { 'Test reason for resolution' }
created_at { Time.current }
encrypted_kms_key { SecureRandom.hex(16) }

after(:build) do |resolution|
resolution.id ||= SecureRandom.random_number(1000)
end

trait :with_expiration do
resolving_type { 'AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration' }
resolving { create(:power_of_attorney_request_expiration) }
end

trait :with_decision do
resolving_type { 'AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision' }
resolving { create(:power_of_attorney_request_decision) }
resolving { create(:power_of_attorney_request_decision, creator: create(:user_account)) }
end

trait :with_declination do
resolving_type { 'AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision' }
reason { "Didn't authorize treatment record disclosure" }
resolving { create(:power_of_attorney_request_decision, creator: create(:user_account)) }
end

trait :with_invalid_type do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,43 @@

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' => '[email protected]', '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]
end
let(:poa_request) { create(:power_of_attorney_request) }
let(:poa_requests) { create_list(:power_of_attorney_request, 3) }

before do
Flipper.enable(:accredited_representative_portal_pilot)
login_as(test_user)
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' do
poa_requests

get('/accredited_representative_portal/v0/power_of_attorney_requests')

expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)).to eq(poa_request_list_mock_data)
parsed_response = JSON.parse(response.body)

expected_response = AccreditedRepresentativePortal::PowerOfAttorneyRequestSerializer
.new(poa_requests)
.serializable_hash

expect(parsed_response.to_json).to eq(expected_response.to_json)
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' do
get("/accredited_representative_portal/v0/power_of_attorney_requests/#{poa_request.id}")

expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)).to include(poa_request_details_mock_data)
parsed_response = JSON.parse(response.body)

expected_response = AccreditedRepresentativePortal::PowerOfAttorneyRequestSerializer
.new(poa_request)
.serializable_hash

expect(parsed_response.to_json).to eq(expected_response.to_json)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require_relative '../../rails_helper'

RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyRequestResolutionSerializer, type: :serializer do
describe 'serialization' do
subject { described_class.new(resolution).serializable_hash[:data][:attributes] }

let(:user) { create(:user_account) }
let(:resolution) do
create(:power_of_attorney_request_resolution, resolving: resolving, reason: 'Did not authorize')
end

context 'when resolving is a Decision' do
let(:resolving) { create(:power_of_attorney_request_decision, type: 'declination', creator: user) }

it 'serializes resolution with decision-specific fields' do
expect(subject).to eq(
id: resolution.id,
created_at: resolution.created_at.iso8601,
reason: 'Did not authorize',
type: 'decision',
decision_type: 'declination',
creator_id: user.id
)
end
end

context 'when resolving is an Expiration' do
let(:resolving) { create(:power_of_attorney_request_expiration) }

it 'serializes resolution with expiration-specific fields' do
expect(subject).to eq(
id: resolution.id,
created_at: resolution.created_at.iso8601,
reason: 'Did not authorize',
type: 'expiration'
)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

require_relative '../../rails_helper'

RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyRequestSerializer, type: :serializer do
describe 'serialization' do
subject { described_class.new(poa_request).serializable_hash[:data][:attributes] }

let(:claimant) { create(:user_account) }
let(:poa_request) { create(:power_of_attorney_request, claimant: claimant, resolution: resolution) }

context 'when resolution exists' do
let(:resolution) do
create(:power_of_attorney_request_resolution,
resolving: create(:power_of_attorney_request_decision, type: 'declination'))
end

it 'serializes POA request with resolution' do
expect(subject).to eq(
id: poa_request.id,
claimant_id: poa_request.claimant_id,
created_at: poa_request.created_at.iso8601,
resolution: {
id: resolution.id,
created_at: resolution.created_at.iso8601,
reason: resolution.reason,
type: 'decision',
decision_type: 'declination',
creator_id: resolution.resolving.creator_id
}
)
end
end

context 'when resolution is absent' do
let(:resolution) { nil }

it 'serializes POA request without resolution' do
expect(subject).to eq(
id: poa_request.id,
claimant_id: poa_request.claimant_id,
created_at: poa_request.created_at.iso8601,
resolution: nil
)
end
end
end
end
Loading