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 #97853 Add Power of Attorney Request Models, Factories, and Specs #19919

Merged
merged 10 commits into from
Dec 19, 2024
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,
ojbucao marked this conversation as resolved.
Show resolved Hide resolved
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
ojbucao marked this conversation as resolved.
Show resolved Hide resolved
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
ojbucao marked this conversation as resolved.
Show resolved Hide resolved
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
Loading