diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index e79083af198..415fde4e8b3 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -5,6 +5,8 @@ on:
push:
branches:
- 'trunk'
+ tags:
+ - 'v*'
pull_request:
branches:
- 'trunk'
diff --git a/app/domain/operations/financial_assistance/on_determination/people/create_or_update.rb b/app/domain/operations/financial_assistance/on_determination/people/create_or_update.rb
index a57cfd416f6..29c84aa4dc9 100644
--- a/app/domain/operations/financial_assistance/on_determination/people/create_or_update.rb
+++ b/app/domain/operations/financial_assistance/on_determination/people/create_or_update.rb
@@ -97,7 +97,6 @@ def update_person(person, applicant)
middle_name: applicant.middle_name,
last_name: applicant.last_name,
name_sfx: applicant.name_sfx,
- encrypted_ssn: applicant.encrypted_ssn,
no_ssn: applicant.no_ssn,
gender: applicant.gender,
dob: applicant.dob,
@@ -116,6 +115,12 @@ def update_person(person, applicant)
is_temporarily_out_of_state: applicant.is_temporarily_out_of_state
)
+ if applicant.encrypted_ssn.blank?
+ person.unset(:encrypted_ssn)
+ else
+ person.encrypted_ssn = applicant.encrypted_ssn
+ end
+
Success(person)
end
diff --git a/app/views/insured/consumer_roles/docs_shared/_country_of_citizenship.html.erb b/app/views/insured/consumer_roles/docs_shared/_country_of_citizenship.html.erb
index 2d4131a787b..2ee98622969 100644
--- a/app/views/insured/consumer_roles/docs_shared/_country_of_citizenship.html.erb
+++ b/app/views/insured/consumer_roles/docs_shared/_country_of_citizenship.html.erb
@@ -2,7 +2,7 @@
<%= v.label :country_of_citizenship, l10n("insured.consumer_roles.docs_shared.country_of_citizenship"), class: "weight-n" %>
<%= v.select :country_of_citizenship,
- options_for_select(::VlpDocument::COUNTRIES_LIST, @country ||= l10n("insured.consumer_roles.docs_shared.country_of_citizenship")),
+ ::VlpDocument::COUNTRIES_LIST,
{ prompt: l10n("insured.consumer_roles.docs_shared.country_of_citizenship") },
{ class: "select_tag", id: "country_of_citizenship" } %>
diff --git a/components/financial_assistance/app/domain/financial_assistance/operations/applications/rrv/income_evidence/request_verification.rb b/components/financial_assistance/app/domain/financial_assistance/operations/applications/rrv/income_evidence/request_verification.rb
index 17eabe2c5a1..af6503befff 100644
--- a/components/financial_assistance/app/domain/financial_assistance/operations/applications/rrv/income_evidence/request_verification.rb
+++ b/components/financial_assistance/app/domain/financial_assistance/operations/applications/rrv/income_evidence/request_verification.rb
@@ -25,13 +25,13 @@ def call(params)
event = yield build_event(cv3_application)
publish(event)
- Success("Successfully published payload for rrv ifsv and created history event | family_eligibility_determination: #{determination_result}")
+ Success("RRV INCOME: Successfully published payload for rrv ifsv and created history event | family_eligibility_determination: #{determination_result}")
end
private
def validate(params)
- return Failure('application_hbx_id is missing') unless params[:application_hbx_id].present?
+ return Failure('RRV INCOME: application_hbx_id is missing') unless params[:application_hbx_id].present?
Success(params[:application_hbx_id])
end
@@ -41,8 +41,7 @@ def fetch_application(application_hbx_id)
if application.present?
Success(application)
else
- rrv_logger.info("No application found with hbx_id #{application_hbx_id}")
- Failure("No application found with hbx_id #{application_hbx_id}")
+ Failure("RRV INCOME: No application found with hbx_id #{application_hbx_id}")
end
end
@@ -50,8 +49,7 @@ def is_application_valid?(application)
if application.valid?
Success(true)
else
- rrv_logger.error("Application with hbx_id #{application.hbx_id} is invalid: #{application.errors.full_messages.join(', ')}")
- Failure("Application with hbx_id #{application.hbx_id} is invalid")
+ Failure("RRV INCOME: Application with hbx_id #{application.hbx_id} is invalid")
end
end
@@ -68,18 +66,21 @@ def transform_and_validate_application(application)
if result.any?(Failure)
errors = result.select { |r| r.is_a?(Failure) }.map(&:failure)
record_application_failure(application, errors)
+ rrv_logger.error("RRV INCOME: Failed to publish: Applicants validation failed with hbx_id #{application.hbx_id} due to #{errors}")
+ update_family_determination(application)
return Failure(errors)
else
application.save!
end
else
record_application_failure(application, payload_entity.failure.messages)
+ rrv_logger.error("RRV INCOME: Failed to publish: Application validation failed with hbx_id #{application.hbx_id} due to #{payload_entity.failure.messages}")
+ update_family_determination(application)
end
payload_entity
rescue StandardError => e
- rrv_logger.error("Failed to transform application with hbx_id #{application.hbx_id} due to #{e.inspect}")
- Failure("Failed to transform application with hbx_id #{application.hbx_id} due to #{e.inspect}")
+ Failure("RRV INCOME: Failed to transform application with hbx_id #{application.hbx_id} due to #{e.inspect}")
end
def validate_applicants(payload_entity)
@@ -114,11 +115,9 @@ def record_histories(application, action, update_reason, update_by)
end
def update_family_determination(application)
- family = application.family
- unless family.present?
- rrv_logger.error("RRV INCOME: Family not found for application hbx_id: #{application.hbx_id}")
- return Failure("RRV INCOME: Family not found for application hbx_id: #{application.hbx_id}")
- end
+ family_id = application.family_id
+ family = Family.where(id: family_id).first
+ return Failure("RRV INCOME: Family not found for application hbx_id: #{application.hbx_id}") unless family.present?
if family.latest_application_gid == application.to_global_id&.uri&.to_s
::Operations::Eligibilities::BuildFamilyDetermination.new.call({family: family})
diff --git a/components/financial_assistance/app/domain/financial_assistance/operations/applications/shared/non_esi_evidence_request.rb b/components/financial_assistance/app/domain/financial_assistance/operations/applications/shared/non_esi_evidence_request.rb
index 322c4a17538..c6c5ef67063 100644
--- a/components/financial_assistance/app/domain/financial_assistance/operations/applications/shared/non_esi_evidence_request.rb
+++ b/components/financial_assistance/app/domain/financial_assistance/operations/applications/shared/non_esi_evidence_request.rb
@@ -39,7 +39,6 @@ def fetch_application(params)
if application.present?
application.valid? ? Success(application) : Failure("Invalid application: #{params[:application_hbx_id]}")
else
- logger.error("No application found with hbx_id #{params[:application_hbx_id]}")
Failure("No application found with hbx_id #{params[:application_hbx_id]}")
end
end
@@ -52,17 +51,19 @@ def transform_and_validate_application(application)
move_applicant_eligibility_state(application)
save_application(application)
return payload_entity if all_applicants_valid.any?(&:last)
+ update_family_determination(application)
return Failure("Failed to transform application with hbx_id #{application.hbx_id} due to all applicants are invalid")
elsif payload_entity.failure?
- move_applicant_eligibility_state(application)
record_application_failure(application, payload_entity.failure.messages)
+ move_applicant_eligibility_state(application)
save_application(application)
+ update_family_determination(application)
+ return Failure("Failed at validation for application with hbx_id #{application.hbx_id} due to #{payload_entity.failure.messages}")
end
payload_entity
rescue StandardError => e
- logger.error("#{process_name} process failed to publish event for the application with hbx_id #{application.hbx_id} due to #{e.inspect}")
- Failure("#{process_name} process failed to publish event for the application with hbx_id #{application.hbx_id} due to #{e.inspect}")
+ Failure("Failed to publish event for the application with hbx_id #{application.hbx_id} due to #{e.inspect}")
end
def move_applicant_eligibility_state(application)
@@ -157,17 +158,13 @@ def save_application(application)
Success(application)
else
error_msg = "Failed to save application: #{application.errors.full_messages.join(', ')}"
- logger.error(error_msg)
Failure(error_msg)
end
end
def update_family_determination(application)
family = application.family
- unless family.present?
- logger.error("#{process_name} Non ESI: Family not found for application hbx_id: #{application.hbx_id}")
- return Failure("#{process_name} Non ESI: Family not found for application hbx_id: #{application.hbx_id}")
- end
+ return Failure("#{process_name} Non ESI: Family not found for application hbx_id: #{application.hbx_id}") unless family.present?
if family.latest_application_gid == application.to_global_id&.uri&.to_s
::Operations::Eligibilities::BuildFamilyDetermination.new.call({family: family})
diff --git a/components/financial_assistance/app/views/financial_assistance/applicants/docs_shared/_country_of_citizenship.html.erb b/components/financial_assistance/app/views/financial_assistance/applicants/docs_shared/_country_of_citizenship.html.erb
index 8749e4bbc8d..8dbe76ad89d 100644
--- a/components/financial_assistance/app/views/financial_assistance/applicants/docs_shared/_country_of_citizenship.html.erb
+++ b/components/financial_assistance/app/views/financial_assistance/applicants/docs_shared/_country_of_citizenship.html.erb
@@ -2,7 +2,7 @@
<%= v.label :country_of_citizenship, l10n("insured.consumer_roles.docs_shared.country_of_citizenship"), class: "weight-n" %>
<%= v.select :country_of_citizenship,
- options_for_select(::VlpDocument::COUNTRIES_LIST, @country ||= l10n("insured.consumer_roles.docs_shared.country_of_citizenship")),
+ ::VlpDocument::COUNTRIES_LIST,
{ prompt: l10n("insured.consumer_roles.docs_shared.country_of_citizenship") },
{ class: "select_tag", id: "country_of_citizenship" } %>
diff --git a/components/financial_assistance/spec/domain/financial_assistance/operations/applications/pvc/non_esi_evidence/request_verification_spec.rb b/components/financial_assistance/spec/domain/financial_assistance/operations/applications/pvc/non_esi_evidence/request_verification_spec.rb
index 4daad5b8eca..de3ec769ab6 100644
--- a/components/financial_assistance/spec/domain/financial_assistance/operations/applications/pvc/non_esi_evidence/request_verification_spec.rb
+++ b/components/financial_assistance/spec/domain/financial_assistance/operations/applications/pvc/non_esi_evidence/request_verification_spec.rb
@@ -9,6 +9,29 @@
let(:person2) { FactoryBot.create(:person, :with_consumer_role, :with_active_consumer_role) }
let(:person3) { FactoryBot.create(:person, :with_consumer_role, :with_active_consumer_role) }
let(:family) { FactoryBot.create(:family, :with_primary_family_member, person: person) }
+ let!(:system_date) { Date.today }
+ let!(:application2) do
+ result = FactoryBot.create(:financial_assistance_application, hbx_id: '300000126', aasm_state: "determined", family_id: family.id, submitted_at: DateTime.new(system_date.year, system_date.month, system_date.day) - 30.minutes)
+ member = FactoryBot.create(:financial_assistance_applicant,
+ eligibility_determination_id: nil,
+ person_hbx_id: person.hbx_id,
+ is_primary_applicant: true,
+ first_name: 'esi',
+ last_name: 'evidence',
+ ssn: "123456789",
+ dob: Date.new(1994,11,17),
+ family_member_id: family.primary_family_member.id,
+ application: result)
+ member.build_aptc_eligibilities_evidences
+ member.build_ivl_eligibility_with_evidences
+ member.save!
+ family.assign_latest_application_gid
+ family.save!
+ ::Operations::Eligibilities::BuildFamilyDetermination.new.call({family: family})
+
+ result
+ end
+
let(:application) do
FactoryBot.create(
:financial_assistance_application,
@@ -16,7 +39,7 @@
aasm_state: 'determined',
assistance_year: TimeKeeper.date_of_record.year,
effective_date: TimeKeeper.date_of_record.beginning_of_year,
- created_at: Date.new(2021, 10, 1)
+ submitted_at: DateTime.new(system_date.year, system_date.month, system_date.day)
)
end
@@ -122,6 +145,7 @@
end
before do
+ allow(EnrollRegistry).to receive(:feature_enabled?).with(:qhp_application).and_return(true)
allow(FinancialAssistanceRegistry).to receive(:feature_enabled?).with(:full_medicaid_determination_step).and_return(false)
allow(FinancialAssistanceRegistry).to receive(:feature_enabled?).with(:indian_alaskan_tribe_details).and_return(false)
allow(FinancialAssistanceRegistry).to receive(:feature_enabled?).with(:non_esi_mec_determination).and_return(true)
@@ -144,6 +168,7 @@
applicant.build_aptc_eligibilities_evidences
end
application.save!
+ family.update_attributes(latest_application_gid: application.to_global_id.uri.to_s)
end
it 'should return success if application hbx_id is passed' do
@@ -338,14 +363,9 @@
it 'handles the exception and returns failure' do
result = subject.call({ application_hbx_id: application.hbx_id })
expect(result).to be_failure
- expect(result.failure).to include('PVC process failed to publish event')
+ expect(result.failure).to match(/Failed to publish event for the application/)
expect(result.failure).to include('Unexpected error')
end
-
- it 'logs the error' do
- expect_any_instance_of(Logger).to receive(:error).with(/PVC process failed to publish event/)
- subject.call({ application_hbx_id: application.hbx_id })
- end
end
end
end
@@ -447,11 +467,6 @@
expect(result).to be_failure
expect(result.failure).to include('Failed to save application: Validation failed: Field is required')
end
-
- it 'logs the error' do
- expect(subject.send(:pvc_logger)).to receive(:error).with(/Failed to save application/)
- subject.send(:save_application, test_application)
- end
end
end
end
@@ -579,6 +594,11 @@
# Make one applicant valid and one invalid
applicant.update_attributes!(ssn: "123456789")
applicant2.update_attributes!(ssn: nil) # Invalid
+ application.applicants.each do |applicant|
+ allow(applicant).to receive(:is_applying_coverage).and_return(true)
+ applicant.build_aptc_eligibilities_evidences
+ end
+ application.save!
end
it 'handles mixed scenarios correctly' do
@@ -613,7 +633,7 @@
result = subject.call({ application_hbx_id: application.hbx_id })
expect(result).to be_failure
- expect(result.failure).to include('PVC process failed to publish event')
+ expect(result.failure).to match(/Failed to publish event for the application with hbx_id/)
expect(result.failure).to include('Event building failed')
# Since the error occurs during transform_and_validate_application (before evidence creation),
diff --git a/components/financial_assistance/spec/domain/financial_assistance/operations/applications/rrv/income_evidence/request_verification_spec.rb b/components/financial_assistance/spec/domain/financial_assistance/operations/applications/rrv/income_evidence/request_verification_spec.rb
index 855a765b9fa..90fbbc27a8f 100644
--- a/components/financial_assistance/spec/domain/financial_assistance/operations/applications/rrv/income_evidence/request_verification_spec.rb
+++ b/components/financial_assistance/spec/domain/financial_assistance/operations/applications/rrv/income_evidence/request_verification_spec.rb
@@ -6,8 +6,14 @@
include Dry::Monads[:do, :result]
let(:person_1) { FactoryBot.create(:person, :with_ssn, :with_consumer_role, :with_active_consumer_role) }
- let(:person_2) { FactoryBot.create(:person, :with_ssn, :with_consumer_role, :with_active_consumer_role) }
+ let(:person_2) do
+ per = FactoryBot.create(:person, :with_ssn, :with_consumer_role, :with_active_consumer_role)
+ person_1.ensure_relationship_with(per, 'spouse')
+ person_1.save!
+ per
+ end
let(:family) { FactoryBot.create(:family, :with_primary_family_member, person: person_1)}
+ let!(:family_member_2) { FactoryBot.create(:family_member, family: family, person: person_2) }
let!(:system_date) { Date.today }
let!(:application2) do
result = FactoryBot.create(:financial_assistance_application, hbx_id: '300000126', aasm_state: "determined", family_id: family.id, submitted_at: DateTime.new(system_date.year, system_date.month, system_date.day) - 30.minutes)
@@ -15,10 +21,10 @@
eligibility_determination_id: nil,
person_hbx_id: person_1.hbx_id,
is_primary_applicant: true,
- first_name: 'esi',
- last_name: 'evidence',
- ssn: "889984400",
- dob: Date.new(1994,11,17),
+ first_name: person_1.first_name,
+ last_name: person_1.last_name,
+ encrypted_ssn: person_1.encrypted_ssn,
+ dob: person_1.dob,
family_member_id: family.primary_family_member.id,
application: result)
member.build_aptc_eligibilities_evidences
@@ -38,34 +44,43 @@
let(:eligibility_determination) { FactoryBot.create(:financial_assistance_eligibility_determination, application: application) }
let(:applicant_1) do
- FactoryBot.build(:financial_assistance_applicant,
- :with_student_information,
- :with_home_address,
- application: application,
- is_primary_applicant: true,
- ssn: '889984400',
- dob: Date.new(1994,11,17),
- first_name: person_1.first_name,
- last_name: person_1.last_name,
- gender: person_1.gender,
- person_hbx_id: person_1.hbx_id,
- eligibility_determination_id: eligibility_determination.id)
+ member = FactoryBot.build(:financial_assistance_applicant,
+ :with_student_information,
+ :with_home_address,
+ application: application,
+ is_primary_applicant: true,
+ encrypted_ssn: person_1.encrypted_ssn,
+ dob: person_1.dob,
+ first_name: person_1.first_name,
+ last_name: person_1.last_name,
+ gender: person_1.gender,
+ person_hbx_id: person_1.hbx_id,
+ eligibility_determination_id: eligibility_determination.id)
+
+ member.build_aptc_eligibilities_evidences
+ member.build_ivl_eligibility_with_evidences
+ member.save!
+ member
end
let(:applicant_2) do
- FactoryBot.build(:financial_assistance_applicant,
- :with_student_information,
- :with_home_address,
- :with_income_evidence,
- application: application,
- is_primary_applicant: false,
- ssn: '889984401',
- dob: Date.new(1996,11,17),
- first_name: person_2.first_name,
- last_name: person_2.last_name,
- gender: person_2.gender,
- person_hbx_id: person_2.hbx_id,
- eligibility_determination_id: eligibility_determination.id)
+ member = FactoryBot.build(:financial_assistance_applicant,
+ :with_student_information,
+ :with_home_address,
+ :with_income_evidence,
+ application: application,
+ is_primary_applicant: false,
+ encrypted_ssn: person_2.encrypted_ssn,
+ dob: person_2.dob,
+ first_name: person_2.first_name,
+ last_name: person_2.last_name,
+ gender: person_2.gender,
+ person_hbx_id: person_2.hbx_id,
+ eligibility_determination_id: eligibility_determination.id)
+ member.build_aptc_eligibilities_evidences
+ member.build_ivl_eligibility_with_evidences
+ member.save!
+ member
end
let(:create_home_address) do
@@ -109,29 +124,20 @@
allow(EnrollRegistry).to receive(:feature_enabled?).with(:qhp_application).and_return(true)
create_home_address
update_benchmark_premiums
- application.applicants.each do |applicant|
- aptc_csr_eligibility = FactoryBot.create(:aptc_csr_eligibility, eligible: applicant, current_state: 'pending')
- old_state = FactoryBot.build(:v3_state_history, created_at: 2.days.ago)
- new_state = FactoryBot.build(:v3_state_history, created_at: 1.day.ago)
- aptc_csr_eligibility.state_histories << old_state
- aptc_csr_eligibility.state_histories << new_state
- aptc_csr_eligibility.save!
- FactoryBot.create(:income_evidence, eligibility: aptc_csr_eligibility, _type: 'FinancialAssistance::Evidences::IncomeEvidence', key: :income_evidence, current_state: 'pending')
- end
-
allow(subject).to receive(:build_event).and_return(event)
allow(subject).to receive(:publish).and_return(Success("Event published successfully"))
allow(HbxProfile).to receive(:current_hbx).and_return hbx_profile
allow(hbx_profile).to receive(:benefit_sponsorship).and_return benefit_sponsorship
allow(benefit_sponsorship).to receive(:current_benefit_period).and_return(benefit_coverage_period)
- family.update_attributes(latest_application_gid: application.to_global_id.uri.to_s)
end
let(:payload_entity) { ::Operations::Fdsh::BuildAndValidateApplicationPayload.new.call(application).value! }
context 'with valid application' do
before do
- allow(EnrollRegistry).to receive(:feature_enabled?).with(:qhp_application).and_return(true)
+ family.remove_instance_variable(:@fetch_latest_determined_application)
+ family.assign_latest_application_gid
+ family.save!
@result = subject.call({application_hbx_id: application.hbx_id})
application.reload
end
@@ -170,12 +176,17 @@
before do
application.applicants.last.unset(:encrypted_ssn)
application.save!
+ family.remove_instance_variable(:@fetch_latest_determined_application)
+ family.assign_latest_application_gid
+ family.save!
@result = subject.call({ application_hbx_id: application.hbx_id })
application.reload
end
it 'should return failure' do
expect(@result).to be_failure
+ family.reload
+ expect(family.eligibility_determination.application_gid).to eq application.to_global_id.uri.to_s
end
it 'should record failure for valid applicant1' do
diff --git a/components/financial_assistance/spec/domain/financial_assistance/operations/applications/rrv/non_esi_evidence/request_verification_spec.rb b/components/financial_assistance/spec/domain/financial_assistance/operations/applications/rrv/non_esi_evidence/request_verification_spec.rb
index 6f00d94afc1..47fe189de5f 100644
--- a/components/financial_assistance/spec/domain/financial_assistance/operations/applications/rrv/non_esi_evidence/request_verification_spec.rb
+++ b/components/financial_assistance/spec/domain/financial_assistance/operations/applications/rrv/non_esi_evidence/request_verification_spec.rb
@@ -40,7 +40,7 @@
aasm_state: 'determined',
assistance_year: TimeKeeper.date_of_record.year,
effective_date: TimeKeeper.date_of_record.beginning_of_year,
- created_at: Date.new(2021, 10, 1)
+ submitted_at: DateTime.new(system_date.year, system_date.month, system_date.day)
)
end
@@ -146,6 +146,7 @@
end
before do
+ allow(EnrollRegistry).to receive(:feature_enabled?).with(:qhp_application).and_return(true)
allow(FinancialAssistanceRegistry).to receive(:feature_enabled?).with(:full_medicaid_determination_step).and_return(false)
allow(FinancialAssistanceRegistry).to receive(:feature_enabled?).with(:indian_alaskan_tribe_details).and_return(false)
allow(FinancialAssistanceRegistry).to receive(:feature_enabled?).with(:non_esi_mec_determination).and_return(true)
@@ -162,7 +163,6 @@
describe '#call' do
context 'success' do
before do
- allow(EnrollRegistry).to receive(:feature_enabled?).with(:qhp_application).and_return(true)
# Build aptc_csr_eligibility for applicants to ensure they can build evidences
application.applicants.each do |applicant|
allow(applicant).to receive(:is_applying_coverage).and_return(true)
@@ -216,11 +216,16 @@
applicant.build_aptc_eligibilities_evidences
end
application.save!
+ family.remove_instance_variable(:@fetch_latest_determined_application)
+ family.assign_latest_application_gid
+ family.save!
end
it 'should return failure if application hbx_id is passed' do
result = subject.call({ application_hbx_id: application.hbx_id })
expect(result).to be_failure
+ family.reload
+ expect(family.eligibility_determination.application_gid).to eq application.to_global_id.uri.to_s
end
it 'builds non_esi_mec_evidence for active applicants before failure processing' do
@@ -444,14 +449,9 @@
it 'handles the exception and returns failure' do
result = subject.call({ application_hbx_id: application.hbx_id })
expect(result).to be_failure
- expect(result.failure).to include('RRV process failed to publish event')
+ expect(result.failure).to match(/Failed to publish event for the application with hbx_id/)
expect(result.failure).to include('Unexpected error')
end
-
- it 'logs the error' do
- expect_any_instance_of(Logger).to receive(:error).with(/RRV process failed to publish event/)
- subject.call({ application_hbx_id: application.hbx_id })
- end
end
end
end
@@ -567,11 +567,6 @@
expect(result).to be_failure
expect(result.failure).to include('Failed to save application: Validation failed: Field is required')
end
-
- it 'logs the error' do
- expect(subject.send(:rrv_logger)).to receive(:error).with(/Failed to save application/)
- subject.send(:save_application, test_application)
- end
end
end
@@ -699,6 +694,11 @@
# Make one applicant valid and one invalid
applicant.update_attributes!(ssn: "123456789")
applicant2.update_attributes!(ssn: nil) # Invalid
+ application.applicants.each do |applicant|
+ allow(applicant).to receive(:is_applying_coverage).and_return(true)
+ applicant.build_aptc_eligibilities_evidences
+ end
+ application.save!
end
it 'handles mixed scenarios correctly' do
@@ -733,7 +733,7 @@
result = subject.call({ application_hbx_id: application.hbx_id })
expect(result).to be_failure
- expect(result.failure).to include('RRV process failed to publish event')
+ expect(result.failure).to match(/Failed to publish event for the application with hbx_id/)
expect(result.failure).to include('Event building failed')
# Since the error occurs during transform_and_validate_application (before evidence creation),
diff --git a/components/financial_assistance/spec/domain/financial_assistance/operations/applications/shared/non_esi_evidence_request_spec.rb b/components/financial_assistance/spec/domain/financial_assistance/operations/applications/shared/non_esi_evidence_request_spec.rb
index c7d51ff49b7..b7d7c273d18 100644
--- a/components/financial_assistance/spec/domain/financial_assistance/operations/applications/shared/non_esi_evidence_request_spec.rb
+++ b/components/financial_assistance/spec/domain/financial_assistance/operations/applications/shared/non_esi_evidence_request_spec.rb
@@ -94,6 +94,7 @@ def handle_validation_failure(errors)
allow(aptc_csr_eligibility).to receive(:non_esi_mec_evidence).and_return(nil)
allow(aptc_csr_eligibility).to receive(:determine_eligibility_state)
+ allow(operation).to receive(:update_family_determination).and_return(Dry::Monads::Success(true))
end
describe '#call' do
@@ -104,7 +105,6 @@ def handle_validation_failure(errors)
before do
allow(operation).to receive(:build_evidence_history).and_return(Dry::Monads::Success(true))
allow(operation).to receive(:transform_and_validate_application).and_return(Dry::Monads::Success(cv3_application))
- allow(operation).to receive(:update_family_determination).and_return(Dry::Monads::Success(true))
allow(operation).to receive(:build_event).and_return(Dry::Monads::Success(event))
end
@@ -356,7 +356,7 @@ def handle_validation_failure(errors)
it 'handles exceptions and returns failure' do
result = operation.send(:transform_and_validate_application, application)
expect(result).to be_failure
- expect(result.failure).to include('test_process process failed')
+ expect(result.failure).to match(/Failed to publish event for the application with/)
end
end
end
diff --git a/components/financial_assistance/spec/views/financial_assistance/applicants/docs_shared/_country_of_citizenship.html.erb_spec.rb b/components/financial_assistance/spec/views/financial_assistance/applicants/docs_shared/_country_of_citizenship.html.erb_spec.rb
new file mode 100644
index 00000000000..6b61629387e
--- /dev/null
+++ b/components/financial_assistance/spec/views/financial_assistance/applicants/docs_shared/_country_of_citizenship.html.erb_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'financial_assistance/applicants/docs_shared/_country_of_citizenship.html.erb', type: :view do
+ let!(:application) do
+ FactoryBot.create(:application,
+ family_id: BSON::ObjectId.new,
+ aasm_state: 'draft',
+ effective_date: Date.today)
+ end
+ let!(:applicant) do
+ FactoryBot.create(:applicant,
+ application: application,
+ dob: Date.today - 40.years,
+ is_primary_applicant: true,
+ family_member_id: BSON::ObjectId.new,
+ country_of_citizenship: country_of_citizenship)
+ end
+ let(:country_of_citizenship) { 'United States' }
+ let(:form_builder) { double('FormBuilder') }
+
+ before do
+ assign(:bs4, true)
+ allow(form_builder).to receive(:object).and_return(applicant)
+ assign(:applicant, applicant)
+ assign(:country, country_of_citizenship)
+ allow(view).to receive(:l10n).with("insured.consumer_roles.docs_shared.country_of_citizenship").and_return("Country of Citizenship")
+ allow(form_builder).to receive(:label).and_return('Country of Citizenship '.html_safe)
+ allow(form_builder).to receive(:select).and_return('... '.html_safe)
+ end
+
+ it 'renders the select field with proper options' do
+ render partial: 'financial_assistance/applicants/docs_shared/country_of_citizenship', locals: { v: form_builder }
+ expect(form_builder).to have_received(:select).with(
+ :country_of_citizenship,
+ ::VlpDocument::COUNTRIES_LIST,
+ { prompt: "Country of Citizenship" },
+ { class: "select_tag", id: "country_of_citizenship" }
+ )
+ end
+
+ context 'when United States is selected' do
+ let(:country_of_citizenship) { 'United States' }
+
+ it 'renders select with United States pre-selected' do
+ select_html = 'Country of Citizenship United States '.html_safe
+ allow(form_builder).to receive(:select).and_return(select_html)
+
+ render partial: 'financial_assistance/applicants/docs_shared/country_of_citizenship', locals: { v: form_builder }
+ expect(rendered).to include('selected="selected">United States')
+ end
+ end
+end
\ No newline at end of file
diff --git a/script/application_renewals/renewal_service.rb b/script/application_renewals/renewal_service.rb
index 3eda24221f5..26384ff0058 100644
--- a/script/application_renewals/renewal_service.rb
+++ b/script/application_renewals/renewal_service.rb
@@ -67,7 +67,7 @@ class RenewalService
def initialize(renewal_year:, primary_person_hbx_ids:)
@renewal_year = renewal_year
@primary_person_hbx_ids = primary_person_hbx_ids
- @logger = Logger.new("#{Rails.root}/log/renewal_service_#{Time.now.strftime('%Y_%m_%d %H_%M_%S')}.log")
+ @logger = Logger.new("#{Rails.root}/log/renewal_service_#{Time.now.strftime('%Y_%m_%d_%H_%M_%S')}.log")
end
# Main entry point.
diff --git a/script/individual_market_eligibility/renewals/generate.rb b/script/individual_market_eligibility/renewals/generate.rb
index e81ff959f43..653a5a262fa 100644
--- a/script/individual_market_eligibility/renewals/generate.rb
+++ b/script/individual_market_eligibility/renewals/generate.rb
@@ -79,7 +79,7 @@ class Generate
def initialize(renewal_year:, primary_person_hbx_ids:)
@renewal_year = renewal_year
@primary_person_hbx_ids = primary_person_hbx_ids
- @qhp_logger = Logger.new("#{Rails.root}/log/qhp_renewal_generated_#{Time.now.strftime('%Y_%m_%d %H_%M_%S')}.log")
+ @qhp_logger = Logger.new("#{Rails.root}/log/qhp_renewal_generated_#{Time.now.strftime('%Y_%m_%d_%H_%M_%S')}.log")
end
def process
@@ -165,8 +165,4 @@ def log_section(hbx_id)
end
end
-if Rails.env.test? || (defined?(Rails) && $PROGRAM_NAME.include?('rails'))
- Script::IndividualMarketEligibility::Renewals::Generate
- .new(renewal_year: renewal_year_arg, primary_person_hbx_ids: primary_hbx_ids_arg)
- .process
-end
\ No newline at end of file
+Script::IndividualMarketEligibility::Renewals::Generate.new(renewal_year: renewal_year_arg, primary_person_hbx_ids: primary_hbx_ids_arg).process
\ No newline at end of file
diff --git a/spec/domain/operations/financial_assistance/on_determination/people/create_or_update_spec.rb b/spec/domain/operations/financial_assistance/on_determination/people/create_or_update_spec.rb
new file mode 100644
index 00000000000..14806ba997b
--- /dev/null
+++ b/spec/domain/operations/financial_assistance/on_determination/people/create_or_update_spec.rb
@@ -0,0 +1,422 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Operations::FinancialAssistance::OnDetermination::People::CreateOrUpdate, type: :model, dbclean: :after_each do
+ let(:family) { FactoryBot.create(:family, :with_primary_family_member, person: primary_person) }
+ let(:application) { FactoryBot.create(:financial_assistance_application, family_id: family.id) }
+ let(:determination) { FactoryBot.create(:financial_assistance_eligibility_determination, application: application) }
+
+ let(:primary_person) { FactoryBot.create(:person, :with_consumer_role) }
+ let(:primary_family_member_id) { family.primary_applicant.id }
+
+ let(:primary_address) do
+ FactoryBot.build(
+ :financial_assistance_address,
+ kind: 'home',
+ address_1: '123 Test St',
+ city: 'Washington',
+ state: 'DC',
+ zip: '20001'
+ )
+ end
+
+ let(:primary_email) { FactoryBot.build(:financial_assistance_email, kind: 'home', address: 'test@example.com') }
+ let(:primary_phone) { FactoryBot.build(:financial_assistance_phone, kind: 'home', area_code: '202', number: '1234567') }
+
+ let(:applicant) do
+ FactoryBot.create(
+ :financial_assistance_applicant,
+ is_primary_applicant: true,
+ family_member_id: primary_family_member_id,
+ person_hbx_id: primary_person.hbx_id,
+ eligibility_determination_id: determination.id,
+ addresses: [primary_address],
+ emails: [primary_email],
+ phones: [primary_phone],
+ application: application,
+ first_name: 'John',
+ last_name: 'Doe',
+ gender: 'male',
+ dob: Date.new(1980, 1, 1),
+ encrypted_ssn: encrypted_ssn,
+ no_ssn: no_ssn
+ )
+ end
+
+ let(:encrypted_ssn) { SymmetricEncryption.encrypt('123456789') }
+ let(:no_ssn) { '0' }
+
+ before :each do
+ allow(EnrollRegistry).to receive(:feature_enabled?).with(:qhp_application).and_return(true)
+ allow(EnrollRegistry[:alive_status].feature).to receive(:is_enabled).and_return(true)
+ end
+
+ describe '#call' do
+ context 'when applicant has an SSN' do
+ let(:encrypted_ssn) { SymmetricEncryption.encrypt('123456789') }
+ let(:no_ssn) { '0' }
+
+ before :each do
+ @result = subject.call(applicant: applicant)
+ end
+
+ it 'returns a success result' do
+ expect(@result.success?).to be_truthy
+ end
+
+ it 'assigns encrypted_ssn to the person' do
+ person = @result.success
+ expect(person.encrypted_ssn).to eq(applicant.encrypted_ssn)
+ expect(person.encrypted_ssn).to be_present
+ end
+
+ it 'assigns the correct SSN value' do
+ person = @result.success
+ expect(person.ssn).to eq('123456789')
+ end
+
+ it 'sets no_ssn flag to false when SSN is present' do
+ person = @result.success
+ expect(person.no_ssn).to eq('0')
+ end
+
+ it 'creates a consumer role with correct attributes' do
+ person = @result.success
+ expect(person.consumer_role).to be_present
+ expect(person.consumer_role.is_applicant).to eq(applicant.is_primary_applicant)
+ expect(person.consumer_role.contact_method).to eq(applicant.contact_method)
+ expect(person.consumer_role.is_applying_coverage).to eq(applicant.is_applying_coverage)
+ end
+
+ it 'creates addresses from applicant information' do
+ person = @result.success
+ expect(person.addresses.size).to eq(1)
+ address = person.addresses.first
+ expect(address.kind).to eq('home')
+ expect(address.address_1).to eq('123 Test St')
+ expect(address.city).to eq('Washington')
+ expect(address.state).to eq('DC')
+ expect(address.zip).to eq('20001')
+ end
+
+ it 'creates emails from applicant information' do
+ person = @result.success
+ expect(person.emails.size).to eq(1)
+ email = person.emails.first
+ expect(email.kind).to eq('home')
+ expect(email.address).to eq('test@example.com')
+ end
+
+ it 'creates phones from applicant information' do
+ person = @result.success
+ expect(person.phones.size).to eq(1)
+ phone = person.phones.first
+ expect(phone.kind).to eq('home')
+ expect(phone.area_code).to eq('202')
+ expect(phone.number).to eq('1234567')
+ end
+
+ it 'creates a demographics group' do
+ person = @result.success
+ expect(person.demographics_group).to be_present
+ end
+
+ it 'persists the person to the database' do
+ person = @result.success
+ expect(person.persisted?).to be_truthy
+ expect(person.id).to be_present
+ end
+
+ context 'when person matching finds existing person with same SSN' do
+ # Skip the main before block for this context to avoid SSN conflicts
+ let!(:existing_person) do
+ Person.destroy_all # Clear any existing people first
+ FactoryBot.create(:person,
+ first_name: 'John',
+ last_name: 'Doe',
+ dob: Date.new(1980, 1, 1),
+ encrypted_ssn: SymmetricEncryption.encrypt('123456789'))
+ end
+
+ # Create a new applicant without family_member_id to force SSN matching
+ let(:matching_applicant) do
+ FactoryBot.create(
+ :financial_assistance_applicant,
+ is_primary_applicant: false,
+ family_member_id: nil, # Key: no family member ID to force SSN matching
+ person_hbx_id: nil,
+ eligibility_determination_id: determination.id,
+ addresses: [primary_address],
+ emails: [primary_email],
+ phones: [primary_phone],
+ application: application,
+ first_name: 'John',
+ last_name: 'Doe',
+ gender: 'male',
+ dob: Date.new(1980, 1, 1),
+ encrypted_ssn: SymmetricEncryption.encrypt('123456789'),
+ no_ssn: '0'
+ )
+ end
+
+ before do
+ # Don't use @result from the main context, run the operation fresh
+ @result = subject.call(applicant: matching_applicant)
+ end
+
+ it 'updates the existing person instead of creating a new one' do
+ person = @result.success
+ expect(person.id).to eq(existing_person.id)
+ end
+
+ it 'updates person attributes from applicant' do
+ person = @result.success
+ expect(person.gender).to eq(matching_applicant.gender)
+ end
+ end
+ end
+
+ context 'when applicant does not have an SSN' do
+ let(:encrypted_ssn) { nil }
+ let(:no_ssn) { '1' }
+
+ before :each do
+ applicant.encrypted_ssn = nil
+ applicant.no_ssn = '1'
+ @result = subject.call(applicant: applicant)
+ end
+
+ it 'returns a success result' do
+ expect(@result.success?).to be_truthy
+ end
+
+ it 'does not assign encrypted_ssn to the person' do
+ person = @result.success
+ expect(person.encrypted_ssn).to be_blank
+ expect(person.encrypted_ssn).to be_nil
+ end
+
+ it 'assigns no_ssn flag to the person' do
+ person = @result.success
+ expect(person.no_ssn).to eq('1')
+ end
+
+ it 'has blank SSN value' do
+ person = @result.success
+ expect(person.ssn).to be_blank
+ expect(person.ssn).to be_nil
+ end
+
+ it 'handles SSN validation correctly for no_ssn cases' do
+ person = @result.success
+ expect(person.no_ssn).to eq('1')
+ expect(person.ssn).to be_nil
+ expect(person.encrypted_ssn).to be_nil
+ end
+
+ it 'still creates a consumer role' do
+ person = @result.success
+ expect(person.consumer_role).to be_present
+ end
+
+ it 'still creates contact information' do
+ person = @result.success
+ expect(person.addresses.size).to eq(1)
+ expect(person.emails.size).to eq(1)
+ expect(person.phones.size).to eq(1)
+ end
+
+ context 'when person matching without SSN' do
+ let!(:existing_person) do
+ FactoryBot.create(:person,
+ first_name: 'John',
+ last_name: 'Doe',
+ dob: Date.new(1980, 1, 1),
+ encrypted_ssn: nil,
+ no_ssn: '1')
+ end
+
+ it 'performs matching using name and DOB only' do
+ person = @result.success
+ expect(person.first_name).to eq('John')
+ expect(person.last_name).to eq('Doe')
+ expect(person.dob).to eq(Date.new(1980, 1, 1))
+ end
+ end
+ end
+
+ context 'when applicant has family_member_id' do
+ let(:existing_person) { FactoryBot.create(:person, :with_consumer_role) }
+ let(:family_member) { FactoryBot.create(:family_member, family: family, person: existing_person) }
+
+ before :each do
+ applicant.family_member_id = family_member.id
+ applicant.person_hbx_id = existing_person.hbx_id
+ @result = subject.call(applicant: applicant)
+ end
+
+ it 'finds the existing person through family member' do
+ person = @result.success
+ expect(person.id).to eq(existing_person.id)
+ end
+
+ it 'updates the existing person with applicant information' do
+ person = @result.success
+ expect(person.first_name).to eq(applicant.first_name)
+ expect(person.last_name).to eq(applicant.last_name)
+ expect(person.gender).to eq(applicant.gender)
+ end
+ end
+
+ context 'when applicant has no VLP document information' do
+ before :each do
+ applicant.vlp_subject = nil
+ applicant.alien_number = nil
+ applicant.i94_number = nil
+ @result = subject.call(applicant: applicant)
+ end
+
+ it 'does not create VLP documents' do
+ person = @result.success
+ expect(person.consumer_role.vlp_documents.size).to eq(0)
+ end
+
+ it 'still creates lawful presence determination with citizen status' do
+ person = @result.success
+ expect(person.consumer_role.lawful_presence_determination).to be_present
+ expect(person.consumer_role.lawful_presence_determination.citizen_status).to eq(applicant.citizen_status)
+ end
+ end
+
+ context 'when updating existing person with different contact information' do
+ let(:existing_person) do
+ person = FactoryBot.create(:person,
+ first_name: 'John',
+ last_name: 'Doe',
+ dob: Date.new(1980, 1, 1),
+ encrypted_ssn: SymmetricEncryption.encrypt('123456789'))
+ person.addresses.build(kind: 'home', address_1: 'Old Address', city: 'Old City', state: 'DC', zip: '20002')
+ person.emails.build(kind: 'home', address: 'old@example.com')
+ person.phones.build(kind: 'home', area_code: '301', number: '9876543')
+ person.save!
+ person
+ end
+
+ before :each do
+ # Force matching to find the existing person
+ operation_instance = described_class.new
+ allow(described_class).to receive(:new).and_return(operation_instance)
+ allow(operation_instance).to receive(:find_existing_person).and_return(existing_person)
+ @result = subject.call(applicant: applicant)
+ end
+
+ it 'replaces old addresses with new ones' do
+ person = @result.success
+ expect(person.addresses.size).to eq(1)
+ address = person.addresses.first
+ expect(address.address_1).to eq('123 Test St')
+ expect(address.city).to eq('Washington')
+ end
+
+ it 'replaces old emails with new ones' do
+ person = @result.success
+ expect(person.emails.size).to eq(1)
+ email = person.emails.first
+ expect(email.address).to eq('test@example.com')
+ end
+
+ it 'replaces old phones with new ones' do
+ person = @result.success
+ expect(person.phones.size).to eq(1)
+ phone = person.phones.first
+ expect(phone.area_code).to eq('202')
+ expect(phone.number).to eq('1234567')
+ end
+ end
+
+ context 'with ethnicity and tribal information' do
+ let(:applicant_with_tribal_info) do
+ FactoryBot.create(
+ :financial_assistance_applicant,
+ is_primary_applicant: false,
+ eligibility_determination_id: determination.id,
+ addresses: [primary_address],
+ emails: [primary_email],
+ phones: [primary_phone],
+ application: application,
+ first_name: 'Tribal',
+ last_name: 'Member',
+ gender: 'female',
+ dob: Date.new(1990, 3, 15),
+ encrypted_ssn: SymmetricEncryption.encrypt('456789012'),
+ ethnicity: ['American Indian or Alaska Native', 'Hispanic or Latino'],
+ race: 'American Indian or Alaska Native',
+ indian_tribe_member: true,
+ tribal_id: 'T12345',
+ tribal_state: 'AK',
+ tribal_name: 'Test Tribe'
+ )
+ end
+
+ before :each do
+ @result = subject.call(applicant: applicant_with_tribal_info)
+ end
+
+ it 'assigns ethnicity information' do
+ person = @result.success
+ expect(person.ethnicity).to contain_exactly('American Indian or Alaska Native', 'Hispanic or Latino')
+ end
+
+ it 'assigns tribal information' do
+ person = @result.success
+ expect(person.race).to eq('American Indian or Alaska Native')
+ expect(person.tribal_id).to eq('T12345')
+ expect(person.tribal_state).to eq('AK')
+ expect(person.tribal_name).to eq('Test Tribe')
+ end
+ end
+ end
+
+ describe 'private methods' do
+ describe '#find_existing_person' do
+ let(:operation) { described_class.new }
+
+ context 'when applicant has family_member_id' do
+ let(:existing_person) { FactoryBot.create(:person) }
+ let(:family_member) { FactoryBot.create(:family_member, family: family, person: existing_person) }
+
+ it 'returns the person through family member' do
+ applicant.family_member_id = family_member.id
+ result = operation.send(:find_existing_person, applicant)
+ expect(result).to eq(existing_person)
+ end
+ end
+
+ context 'when applicant has no family_member_id' do
+ it 'uses matching criteria to find person' do
+ # Test the matching logic
+ result = operation.send(:find_existing_person, applicant)
+ # Since there's no exact match, it should return nil or use the matching service
+ expect(result).to be_a(Person).or be_nil
+ end
+ end
+ end
+
+ describe '#fetch_ethnicity' do
+ let(:operation) { described_class.new }
+
+ it 'filters out blank ethnicity values' do
+ applicant.ethnicity = ['Hispanic or Latino', '', nil, 'American Indian or Alaska Native', '']
+ result = operation.send(:fetch_ethnicity, applicant)
+ expect(result).to contain_exactly('Hispanic or Latino', 'American Indian or Alaska Native')
+ end
+
+ it 'returns empty array for non-array ethnicity' do
+ applicant.ethnicity = nil
+ result = operation.send(:fetch_ethnicity, applicant)
+ expect(result).to eq([])
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/views/insured/consumer_roles/docs_shared/_country_of_citizenship.html.erb_spec.rb b/spec/views/insured/consumer_roles/docs_shared/_country_of_citizenship.html.erb_spec.rb
new file mode 100644
index 00000000000..36b8a7a695e
--- /dev/null
+++ b/spec/views/insured/consumer_roles/docs_shared/_country_of_citizenship.html.erb_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'insured/consumer_roles/docs_shared/_country_of_citizenship.html.erb', type: :view do
+ let(:person) { FactoryBot.create(:person, :with_consumer_role) }
+ let(:consumer_role) { person.consumer_role }
+ let(:country_of_citizenship) { 'United States' }
+ let(:form_builder) { double('FormBuilder') }
+
+ before do
+ allow(form_builder).to receive(:object).and_return(consumer_role)
+ assign(:country, country_of_citizenship)
+ allow(view).to receive(:l10n).with("insured.consumer_roles.docs_shared.country_of_citizenship").and_return("Country of Citizenship")
+ allow(form_builder).to receive(:label).and_return('Country of Citizenship '.html_safe)
+ allow(form_builder).to receive(:select).and_return('... '.html_safe)
+ end
+
+ context 'when bs4 is enabled' do
+ before do
+ assign(:bs4, true)
+ end
+
+ it 'renders the select field with proper options' do
+ render partial: 'insured/consumer_roles/docs_shared/country_of_citizenship', locals: { v: form_builder }
+ expect(form_builder).to have_received(:select).with(
+ :country_of_citizenship,
+ ::VlpDocument::COUNTRIES_LIST,
+ { prompt: "Country of Citizenship" },
+ { class: "select_tag", id: "country_of_citizenship" }
+ )
+ end
+
+ context 'when United States is selected' do
+ let(:country_of_citizenship) { 'United States' }
+
+ it 'renders select with United States pre-selected' do
+ select_html = 'Country of Citizenship United States '.html_safe
+ allow(form_builder).to receive(:select).and_return(select_html)
+
+ render partial: 'insured/consumer_roles/docs_shared/country_of_citizenship', locals: { v: form_builder }
+ expect(rendered).to include('selected="selected">United States')
+ end
+ end
+ end
+end
\ No newline at end of file