From 2afd9d7879f411a1f06da1e8405a8287cf39a810 Mon Sep 17 00:00:00 2001 From: OJ Bucao Date: Mon, 16 Dec 2024 14:03:57 -0800 Subject: [PATCH 1/8] Add PowerOfAttorneyRequest model, factory, and specs --- .../power_of_attorney_request.rb | 21 +++++++++++++++++ .../factories/power_of_attorney_request.rb | 9 ++++++++ .../power_of_attorney_request_spec.rb | 23 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request.rb create mode 100644 modules/accredited_representative_portal/spec/factories/power_of_attorney_request.rb create mode 100644 modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_spec.rb diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request.rb new file mode 100644 index 00000000000..0d796feaa62 --- /dev/null +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module AccreditedRepresentativePortal + class PowerOfAttorneyRequest < ApplicationRecord + belongs_to :claimant, + class_name: 'UserAccount' + + has_one :form, + class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyForm', + inverse_of: :power_of_attorney_request, + dependent: :destroy + + has_one :resolution, + class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestResolution', + inverse_of: :power_of_attorney_request, + dependent: :destroy + + # Validations + validates :created_at, presence: true + end +end diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_request.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request.rb new file mode 100644 index 00000000000..0aa23eea4f6 --- /dev/null +++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request.rb @@ -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 diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_spec.rb new file mode 100644 index 00000000000..f804b1e7ced --- /dev/null +++ b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyRequest, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:claimant).class_name('UserAccount') } + it { is_expected.to have_one(:form).dependent(:destroy) } + it { is_expected.to have_one(:resolution).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:created_at) } + end + + describe 'creation' do + it 'creates a valid record' do + user = UserAccount.create!(id: SecureRandom.uuid) + request = build(:power_of_attorney_request, claimant: user) + expect(request).to be_valid + end + end +end From be763b860a0249a852561bfddb85af4e676b757d Mon Sep 17 00:00:00 2001 From: OJ Bucao Date: Mon, 16 Dec 2024 14:03:58 -0800 Subject: [PATCH 2/8] Add PowerOfAttorneyRequestDecision model, factory, and specs --- .../power_of_attorney_request_decision.rb | 15 ++++++++++++ .../factories/power_of_attorney_decision.rb | 10 ++++++++ ...power_of_attorney_request_decision_spec.rb | 23 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb create mode 100644 modules/accredited_representative_portal/spec/factories/power_of_attorney_decision.rb create mode 100644 modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_decision_spec.rb diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb new file mode 100644 index 00000000000..76cbae91d9e --- /dev/null +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module AccreditedRepresentativePortal + class PowerOfAttorneyRequestDecision < ApplicationRecord + belongs_to :creator, + class_name: 'UserAccount' + + has_one :power_of_attorney_request_resolution, + as: :resolving, + inverse_of: :resolving, + dependent: :destroy + + validates :type, presence: true, length: { maximum: 255 } + end +end diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_decision.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_decision.rb new file mode 100644 index 00000000000..fd6f2e65d66 --- /dev/null +++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_decision.rb @@ -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 diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_decision_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_decision_spec.rb new file mode 100644 index 00000000000..bcb488b408c --- /dev/null +++ b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_decision_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require '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).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:type) } + it { is_expected.to validate_length_of(:type).is_at_most(255) } + end + + describe 'creation' do + it 'creates a valid record' do + user = UserAccount.create!(id: SecureRandom.uuid) + decision = build(:power_of_attorney_request_decision, creator: user) + expect(decision).to be_valid + end + end +end From ec5dac484be37852a5cb90afa6ec1851b62dbef3 Mon Sep 17 00:00:00 2001 From: OJ Bucao Date: Mon, 16 Dec 2024 14:03:58 -0800 Subject: [PATCH 3/8] Add PowerOfAttorneyRequestExpiration model, factory, and specs --- .../power_of_attorney_request_expiration.rb | 13 +++++++++++++ .../factories/power_of_attorney_expiration.rb | 8 ++++++++ .../power_of_attorney_request_expiration_spec.rb | 16 ++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb create mode 100644 modules/accredited_representative_portal/spec/factories/power_of_attorney_expiration.rb create mode 100644 modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_expiration_spec.rb diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb new file mode 100644 index 00000000000..56c88e674c7 --- /dev/null +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module AccreditedRepresentativePortal + class PowerOfAttorneyRequestExpiration < ApplicationRecord + has_one :power_of_attorney_request_resolution, + as: :resolving, + inverse_of: :resolving, + dependent: :destroy + + # Validations + validates :id, presence: true + end +end diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_expiration.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_expiration.rb new file mode 100644 index 00000000000..2f294a2a9fb --- /dev/null +++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_expiration.rb @@ -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 diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_expiration_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_expiration_spec.rb new file mode 100644 index 00000000000..5664afacbaf --- /dev/null +++ b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_expiration_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration, type: :model do + describe 'associations' do + it { is_expected.to have_one(:power_of_attorney_request_resolution).dependent(:destroy) } + 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 From 85a0c79be185ef9632f7fce5f9009f7f31a945c0 Mon Sep 17 00:00:00 2001 From: OJ Bucao Date: Mon, 16 Dec 2024 14:03:58 -0800 Subject: [PATCH 4/8] Add PowerOfAttorneyRequestResolution model, factory, and specs --- .../power_of_attorney_request_resolution.rb | 26 +++++++ .../power_of_attorney_request_resolution.rb | 22 ++++++ ...wer_of_attorney_request_resolution_spec.rb | 75 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb create mode 100644 modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb create mode 100644 modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_resolution_spec.rb diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb new file mode 100644 index 00000000000..d3a9835455f --- /dev/null +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb @@ -0,0 +1,26 @@ +# 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, dependent: :destroy, optional: true + + has_kms_key + + has_encrypted :reason, key: :kms_key, **lockbox_options + + # Validations + validates :power_of_attorney_request_id, uniqueness: true + validates :resolving_type, presence: true, inclusion: { in: RESOLVING_TYPES, allow_nil: true } + validates :resolving_id, presence: true, if: -> { resolving_type.present? } + validates :created_at, presence: true + end +end diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb new file mode 100644 index 00000000000..5f0a392a5d5 --- /dev/null +++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb @@ -0,0 +1,22 @@ +# 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 + end +end diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_resolution_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_resolution_spec.rb new file mode 100644 index 00000000000..e6e21e8580b --- /dev/null +++ b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_resolution_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AccreditedRepresentativePortal::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 'validations' do + subject { create(:power_of_attorney_request_resolution, :with_decision) } + + it { is_expected.to validate_uniqueness_of(:power_of_attorney_request_id).ignoring_case_sensitivity } + it { is_expected.to validate_inclusion_of(:resolving_type).in_array(described_class::RESOLVING_TYPES) } + + it 'validates presence of resolving_id if resolving_type is present' do + resolution = build(:power_of_attorney_request_resolution, resolving_type: described_class::RESOLVING_TYPES.first, + resolving_id: nil) + expect(resolution).not_to be_valid + expect(resolution.errors[:resolving_id]).to include("can't be blank") + 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(AccreditedRepresentativePortal::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(AccreditedRepresentativePortal::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 + + it 'is invalid with invalid resolving_type' do + resolution = build(:power_of_attorney_request_resolution, resolving_type: 'invalid_type') + expect(resolution).not_to be_valid + end + end +end From 8dc165515363965812fe31b15f5cd365fec71e9f Mon Sep 17 00:00:00 2001 From: OJ Bucao Date: Mon, 16 Dec 2024 14:07:09 -0800 Subject: [PATCH 5/8] Add PowerOfAttorneyForm model, factory, and specs --- .../power_of_attorney_form.rb | 24 ++++++++++++++ .../spec/factories/power_of_attorney_form.rb | 12 +++++++ .../power_of_attorney_form_spec.rb | 31 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb create mode 100644 modules/accredited_representative_portal/spec/factories/power_of_attorney_form.rb create mode 100644 modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_form_spec.rb diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb new file mode 100644 index 00000000000..9e048ca87bf --- /dev/null +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module AccreditedRepresentativePortal + class PowerOfAttorneyForm < ApplicationRecord + belongs_to :power_of_attorney_request, + class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyRequest', + inverse_of: :form + + has_kms_key + + has_encrypted :data, key: :kms_key, **lockbox_options + + blind_index :city + blind_index :state + blind_index :zipcode + + # Validations + validates :power_of_attorney_request_id, uniqueness: true + validates :data_ciphertext, presence: true + validates :city_bidx, presence: true, length: { is: 44 } + validates :state_bidx, presence: true, length: { is: 44 } + validates :zipcode_bidx, presence: true, length: { is: 44 } + end +end diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_form.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_form.rb new file mode 100644 index 00000000000..779efce161d --- /dev/null +++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_form.rb @@ -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 diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_form_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_form_spec.rb new file mode 100644 index 00000000000..22d349918f4 --- /dev/null +++ b/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_form_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyForm, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:power_of_attorney_request) } + end + + describe 'validations' do + it 'validates uniqueness of power_of_attorney_request_id' do + form = create(:power_of_attorney_form) + duplicate_form = build(:power_of_attorney_form, power_of_attorney_request: form.power_of_attorney_request) + + expect(duplicate_form).not_to be_valid + expect(duplicate_form.errors[:power_of_attorney_request_id]).to include('has already been taken') + end + + it { is_expected.to validate_presence_of(:data_ciphertext) } + it { is_expected.to validate_length_of(:city_bidx).is_equal_to(44) } + it { is_expected.to validate_length_of(:state_bidx).is_equal_to(44) } + it { is_expected.to validate_length_of(:zipcode_bidx).is_equal_to(44) } + 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 From f8472623778a0915b2c4b323ff8342d407e1582f Mon Sep 17 00:00:00 2001 From: OJ Bucao Date: Mon, 16 Dec 2024 14:09:10 -0800 Subject: [PATCH 6/8] (fix) Rename directory due to misspelling --- .../power_of_attorney_form_spec.rb | 0 .../power_of_attorney_request_decision_spec.rb | 0 .../power_of_attorney_request_expiration_spec.rb | 0 .../power_of_attorney_request_resolution_spec.rb | 0 .../power_of_attorney_request_spec.rb | 0 .../representative_user_spec.rb | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename modules/accredited_representative_portal/spec/models/{accredited_representatiive_portal => accredited_representative_portal}/power_of_attorney_form_spec.rb (100%) rename modules/accredited_representative_portal/spec/models/{accredited_representatiive_portal => accredited_representative_portal}/power_of_attorney_request_decision_spec.rb (100%) rename modules/accredited_representative_portal/spec/models/{accredited_representatiive_portal => accredited_representative_portal}/power_of_attorney_request_expiration_spec.rb (100%) rename modules/accredited_representative_portal/spec/models/{accredited_representatiive_portal => accredited_representative_portal}/power_of_attorney_request_resolution_spec.rb (100%) rename modules/accredited_representative_portal/spec/models/{accredited_representatiive_portal => accredited_representative_portal}/power_of_attorney_request_spec.rb (100%) rename modules/accredited_representative_portal/spec/models/{accredited_representatiive_portal => accredited_representative_portal}/representative_user_spec.rb (100%) diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_form_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_form_spec.rb similarity index 100% rename from modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_form_spec.rb rename to modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_form_spec.rb diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_decision_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_decision_spec.rb similarity index 100% rename from modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_decision_spec.rb rename to modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_decision_spec.rb diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_expiration_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_expiration_spec.rb similarity index 100% rename from modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_expiration_spec.rb rename to modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_expiration_spec.rb diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_resolution_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_resolution_spec.rb similarity index 100% rename from modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_resolution_spec.rb rename to modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_resolution_spec.rb diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_spec.rb similarity index 100% rename from modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/power_of_attorney_request_spec.rb rename to modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_spec.rb diff --git a/modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/representative_user_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/representative_user_spec.rb similarity index 100% rename from modules/accredited_representative_portal/spec/models/accredited_representatiive_portal/representative_user_spec.rb rename to modules/accredited_representative_portal/spec/models/accredited_representative_portal/representative_user_spec.rb From 3e79244072a2b88e81376eec1e08d5109de298ee Mon Sep 17 00:00:00 2001 From: OJ Bucao Date: Tue, 17 Dec 2024 11:28:47 -0800 Subject: [PATCH 7/8] (fix) Add table name prefix override and ActiveRecord dependency for POA requests - Override `table_name_prefix` to 'ar_' for models in the Accredited Representative Portal - 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. --- .../accredited_representative_portal.gemspec | 1 + .../lib/accredited_representative_portal/engine.rb | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/modules/accredited_representative_portal/accredited_representative_portal.gemspec b/modules/accredited_representative_portal/accredited_representative_portal.gemspec index 59ff0751b94..0a163557ead 100644 --- a/modules/accredited_representative_portal/accredited_representative_portal.gemspec +++ b/modules/accredited_representative_portal/accredited_representative_portal.gemspec @@ -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 diff --git a/modules/accredited_representative_portal/lib/accredited_representative_portal/engine.rb b/modules/accredited_representative_portal/lib/accredited_representative_portal/engine.rb index 9d1284ad819..864b156a223 100644 --- a/modules/accredited_representative_portal/lib/accredited_representative_portal/engine.rb +++ b/modules/accredited_representative_portal/lib/accredited_representative_portal/engine.rb @@ -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. From f56149d77535d9647d60477c1abff2cfab491343 Mon Sep 17 00:00:00 2001 From: OJ Bucao Date: Tue, 17 Dec 2024 15:19:10 -0800 Subject: [PATCH 8/8] (fix) Apply requested PR changes; all specs passing --- .../power_of_attorney_form.rb | 9 +- .../power_of_attorney_request.rb | 14 +-- .../power_of_attorney_request_decision.rb | 11 +- .../power_of_attorney_request_expiration.rb | 8 +- .../power_of_attorney_request_resolution.rb | 14 ++- .../power_of_attorney_request_resolution.rb | 29 +++++ .../power_of_attorney_form_spec.rb | 17 +-- ...power_of_attorney_request_decision_spec.rb | 17 +-- ...wer_of_attorney_request_expiration_spec.rb | 4 +- ...wer_of_attorney_request_resolution_spec.rb | 109 ++++++++++++++---- .../power_of_attorney_request_spec.rb | 18 +-- 11 files changed, 143 insertions(+), 107 deletions(-) diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb index 9e048ca87bf..b9d70b73e25 100644 --- a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_form.rb @@ -4,7 +4,7 @@ module AccreditedRepresentativePortal class PowerOfAttorneyForm < ApplicationRecord belongs_to :power_of_attorney_request, class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyRequest', - inverse_of: :form + inverse_of: :power_of_attorney_form has_kms_key @@ -13,12 +13,5 @@ class PowerOfAttorneyForm < ApplicationRecord blind_index :city blind_index :state blind_index :zipcode - - # Validations - validates :power_of_attorney_request_id, uniqueness: true - validates :data_ciphertext, presence: true - validates :city_bidx, presence: true, length: { is: 44 } - validates :state_bidx, presence: true, length: { is: 44 } - validates :zipcode_bidx, presence: true, length: { is: 44 } end end diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request.rb index 0d796feaa62..18af415b22c 100644 --- a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request.rb +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request.rb @@ -2,20 +2,14 @@ module AccreditedRepresentativePortal class PowerOfAttorneyRequest < ApplicationRecord - belongs_to :claimant, - class_name: 'UserAccount' + belongs_to :claimant, class_name: 'UserAccount' - has_one :form, + has_one :power_of_attorney_form, class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyForm', - inverse_of: :power_of_attorney_request, - dependent: :destroy + inverse_of: :power_of_attorney_request has_one :resolution, class_name: 'AccreditedRepresentativePortal::PowerOfAttorneyRequestResolution', - inverse_of: :power_of_attorney_request, - dependent: :destroy - - # Validations - validates :created_at, presence: true + inverse_of: :power_of_attorney_request end end diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb index 76cbae91d9e..6dcc0a03e41 100644 --- a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_decision.rb @@ -2,14 +2,11 @@ module AccreditedRepresentativePortal class PowerOfAttorneyRequestDecision < ApplicationRecord - belongs_to :creator, - class_name: 'UserAccount' + include PowerOfAttorneyRequestResolution::Resolving - has_one :power_of_attorney_request_resolution, - as: :resolving, - inverse_of: :resolving, - dependent: :destroy + self.inheritance_column = nil - validates :type, presence: true, length: { maximum: 255 } + belongs_to :creator, + class_name: 'UserAccount' end end diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb index 56c88e674c7..a2a6fd4cd9e 100644 --- a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_expiration.rb @@ -2,12 +2,6 @@ module AccreditedRepresentativePortal class PowerOfAttorneyRequestExpiration < ApplicationRecord - has_one :power_of_attorney_request_resolution, - as: :resolving, - inverse_of: :resolving, - dependent: :destroy - - # Validations - validates :id, presence: true + include PowerOfAttorneyRequestResolution::Resolving end end diff --git a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb index d3a9835455f..96754cbb86d 100644 --- a/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb +++ b/modules/accredited_representative_portal/app/models/accredited_representative_portal/power_of_attorney_request_resolution.rb @@ -11,16 +11,18 @@ class PowerOfAttorneyRequestResolution < ApplicationRecord 'AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision' ].freeze - delegated_type :resolving, types: RESOLVING_TYPES, dependent: :destroy, optional: true + delegated_type :resolving, types: RESOLVING_TYPES has_kms_key has_encrypted :reason, key: :kms_key, **lockbox_options - # Validations - validates :power_of_attorney_request_id, uniqueness: true - validates :resolving_type, presence: true, inclusion: { in: RESOLVING_TYPES, allow_nil: true } - validates :resolving_id, presence: true, if: -> { resolving_type.present? } - validates :created_at, presence: true + module Resolving + extend ActiveSupport::Concern + + included do + has_one :power_of_attorney_request_resolution, as: :resolving + end + end end end diff --git a/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb index 5f0a392a5d5..20798bf1d54 100644 --- a/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb +++ b/modules/accredited_representative_portal/spec/factories/power_of_attorney_request_resolution.rb @@ -18,5 +18,34 @@ 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 diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_form_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_form_spec.rb index 22d349918f4..0767cf0ceca 100644 --- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_form_spec.rb +++ b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_form_spec.rb @@ -1,27 +1,12 @@ # frozen_string_literal: true -require 'rails_helper' +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 'validations' do - it 'validates uniqueness of power_of_attorney_request_id' do - form = create(:power_of_attorney_form) - duplicate_form = build(:power_of_attorney_form, power_of_attorney_request: form.power_of_attorney_request) - - expect(duplicate_form).not_to be_valid - expect(duplicate_form.errors[:power_of_attorney_request_id]).to include('has already been taken') - end - - it { is_expected.to validate_presence_of(:data_ciphertext) } - it { is_expected.to validate_length_of(:city_bidx).is_equal_to(44) } - it { is_expected.to validate_length_of(:state_bidx).is_equal_to(44) } - it { is_expected.to validate_length_of(:zipcode_bidx).is_equal_to(44) } - end - describe 'creation' do it 'creates a valid form' do form = build(:power_of_attorney_form, data_ciphertext: 'test_data') diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_decision_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_decision_spec.rb index bcb488b408c..c2376dc7ecd 100644 --- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_decision_spec.rb +++ b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_decision_spec.rb @@ -1,23 +1,10 @@ # frozen_string_literal: true -require 'rails_helper' +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).dependent(:destroy) } - end - - describe 'validations' do - it { is_expected.to validate_presence_of(:type) } - it { is_expected.to validate_length_of(:type).is_at_most(255) } - end - - describe 'creation' do - it 'creates a valid record' do - user = UserAccount.create!(id: SecureRandom.uuid) - decision = build(:power_of_attorney_request_decision, creator: user) - expect(decision).to be_valid - end + it { is_expected.to have_one(:power_of_attorney_request_resolution) } end end diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_expiration_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_expiration_spec.rb index 5664afacbaf..2213312df20 100644 --- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_expiration_spec.rb +++ b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_expiration_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'rails_helper' +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).dependent(:destroy) } + it { is_expected.to have_one(:power_of_attorney_request_resolution) } end describe 'validations' do diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_resolution_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_resolution_spec.rb index e6e21e8580b..e7a006aac77 100644 --- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_resolution_spec.rb +++ b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_resolution_spec.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true -require 'rails_helper' +require_relative '../../rails_helper' -RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyRequestResolution, type: :model do +mod = AccreditedRepresentativePortal +RSpec.describe mod::PowerOfAttorneyRequestResolution, type: :model do describe 'associations' do let(:power_of_attorney_request) { create(:power_of_attorney_request) } @@ -35,41 +36,107 @@ end end - describe 'validations' do - subject { create(:power_of_attorney_request_resolution, :with_decision) } - - it { is_expected.to validate_uniqueness_of(:power_of_attorney_request_id).ignoring_case_sensitivity } - it { is_expected.to validate_inclusion_of(:resolving_type).in_array(described_class::RESOLVING_TYPES) } - - it 'validates presence of resolving_id if resolving_type is present' do - resolution = build(:power_of_attorney_request_resolution, resolving_type: described_class::RESOLVING_TYPES.first, - resolving_id: nil) - expect(resolution).not_to be_valid - expect(resolution.errors[:resolving_id]).to include("can't be blank") - 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(AccreditedRepresentativePortal::PowerOfAttorneyRequestExpiration) + 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(AccreditedRepresentativePortal::PowerOfAttorneyRequestDecision) + 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 - it 'is invalid with invalid resolving_type' do - resolution = build(:power_of_attorney_request_resolution, resolving_type: 'invalid_type') - expect(resolution).not_to be_valid + 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 diff --git a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_spec.rb b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_spec.rb index f804b1e7ced..81b29d90317 100644 --- a/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_spec.rb +++ b/modules/accredited_representative_portal/spec/models/accredited_representative_portal/power_of_attorney_request_spec.rb @@ -1,23 +1,11 @@ # frozen_string_literal: true -require 'rails_helper' +require_relative '../../rails_helper' RSpec.describe AccreditedRepresentativePortal::PowerOfAttorneyRequest, type: :model do describe 'associations' do it { is_expected.to belong_to(:claimant).class_name('UserAccount') } - it { is_expected.to have_one(:form).dependent(:destroy) } - it { is_expected.to have_one(:resolution).dependent(:destroy) } - end - - describe 'validations' do - it { is_expected.to validate_presence_of(:created_at) } - end - - describe 'creation' do - it 'creates a valid record' do - user = UserAccount.create!(id: SecureRandom.uuid) - request = build(:power_of_attorney_request, claimant: user) - expect(request).to be_valid - end + it { is_expected.to have_one(:power_of_attorney_form) } + it { is_expected.to have_one(:resolution) } end end