Skip to content

Commit

Permalink
Issue #97853 Add Power of Attorney Request Models, Factories, and Spe…
Browse files Browse the repository at this point in the history
…cs (#19919)

* (feat) Add PowerOfAttorneyRequest model, factory, and specs

* (feat) Add PowerOfAttorneyRequestDecision model, factory, and specs

* (feat) Add PowerOfAttorneyRequestExpiration model, factory, and specs

* (feat) Add PowerOfAttorneyRequestResolution model, factory, and specs

* (feat) Add PowerOfAttorneyForm model, factory, and specs

* (fix) Rename directory due to misspelling

* (fix) Add table name prefix override and ActiveRecord dependency for POA requests

- (feat) Override `table_name_prefix` to 'ar_' for models in the Accredited Representative Portal
- (feat) Add `activerecord` as a development dependency to fix spec issues

This ensures Power of Attorney request models in the engine use the correct table prefix
and that all specs pass successfully.

* (fix) Apply requested PR changes; all specs passing
  • Loading branch information
ojbucao authored Dec 19, 2024
1 parent df07eb3 commit 365ef65
Show file tree
Hide file tree
Showing 18 changed files with 375 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ Gem::Specification.new do |spec|
spec.test_files = Dir['spec/**/*']

spec.add_dependency 'blind_index'
spec.add_development_dependency 'activerecord'
spec.add_development_dependency 'rspec-rails'
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module AccreditedRepresentativePortal
class PowerOfAttorneyForm < ApplicationRecord
belongs_to :power_of_attorney_request,
class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyRequest',
inverse_of: :power_of_attorney_form

has_kms_key

has_encrypted :data, key: :kms_key, **lockbox_options

blind_index :city
blind_index :state
blind_index :zipcode
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module AccreditedRepresentativePortal
class PowerOfAttorneyRequest < ApplicationRecord
belongs_to :claimant, class_name: 'UserAccount'

has_one :power_of_attorney_form,
class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyForm',
inverse_of: :power_of_attorney_request

has_one :resolution,
class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestResolution',
inverse_of: :power_of_attorney_request
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

module AccreditedRepresentativePortal
class PowerOfAttorneyRequestDecision < ApplicationRecord
include PowerOfAttorneyRequestResolution::Resolving

self.inheritance_column = nil

belongs_to :creator,
class_name: 'UserAccount'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module AccreditedRepresentativePortal
class PowerOfAttorneyRequestExpiration < ApplicationRecord
include PowerOfAttorneyRequestResolution::Resolving
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module AccreditedRepresentativePortal
class PowerOfAttorneyRequestResolution < ApplicationRecord
belongs_to :power_of_attorney_request,
class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyRequest',
inverse_of: :resolution

RESOLVING_TYPES = [
'AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration',
'AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision'
].freeze

delegated_type :resolving, types: RESOLVING_TYPES

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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
module AccreditedRepresentativePortal
class Engine < ::Rails::Engine
isolate_namespace AccreditedRepresentativePortal

# `isolate_namespace` redefines `table_name_prefix` on load of
# `active_record`, so we append our own callback to redefine it again how we
# want.
ActiveSupport.on_load(:active_record) do
AccreditedRepresentativePortal.redefine_singleton_method(:table_name_prefix) do
'ar_'
end
end

config.generators.api_only = true

# So that the app-wide migration command notices our engine's migrations.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

FactoryBot.define do
factory :power_of_attorney_request_expiration,
class: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration' do
id { Faker::Internet.uuid }
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

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 }
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

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) }

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) }
end

trait :with_invalid_type do
resolving_type { 'AccreditedRepresentativePortal::InvalidType' }
resolving { AccreditedRepresentativePortal::InvalidType.new }
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# 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
Loading

0 comments on commit 365ef65

Please sign in to comment.