Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions app/jobs/get_usps_proofing_results_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ class GetUspsProofingResultsJob < ApplicationJob
IPP_STATUS_PASSED = 'In-person passed'
IPP_STATUS_FAILED = 'In-person failed'
IPP_INCOMPLETE_ERROR_MESSAGE = 'Customer has not been to a post office to complete IPP'
IPP_EXPIRED_ERROR_MESSAGE = 'More than 30 days have passed since opt-in to IPP'
IPP_EXPIRED_ERROR_MESSAGE = /More than (?<days>\d+) days have passed since opt-in to IPP/
IPP_INVALID_ENROLLMENT_CODE_MESSAGE = 'Enrollment code %s does not exist'
IPP_INVALID_APPLICANT_MESSAGE = 'Applicant %s does not exist'
SUPPORTED_ID_TYPES = [
"State driver's license",
"State non-driver's identification card",
Expand Down Expand Up @@ -138,18 +140,25 @@ def analytics(user: AnonymousUser.new)
end

def handle_bad_request_error(err, enrollment)
response = err.response_body
case response&.[]('responseMessage')
when IPP_INCOMPLETE_ERROR_MESSAGE
response_body = err.response_body
response_message = response_body&.[]('responseMessage')

if response_message == IPP_INCOMPLETE_ERROR_MESSAGE
# Customer has not been to post office for IPP
enrollment_outcomes[:enrollments_in_progress] += 1
when IPP_EXPIRED_ERROR_MESSAGE
handle_expired_status_update(enrollment, err.response)
elsif response_message&.match(IPP_EXPIRED_ERROR_MESSAGE)
handle_expired_status_update(enrollment, err.response, response_message)
elsif response_message == IPP_INVALID_ENROLLMENT_CODE_MESSAGE % enrollment.enrollment_code
handle_unexpected_response(enrollment, response_message, reason: 'Invalid enrollment code')
elsif response_message == IPP_INVALID_APPLICANT_MESSAGE % enrollment.unique_id
handle_unexpected_response(
enrollment, response_message, reason: 'Invalid applicant unique id'
)
else
NewRelic::Agent.notice_error(err)
analytics(user: enrollment.user).idv_in_person_usps_proofing_results_job_exception(
**enrollment_analytics_attributes(enrollment, complete: false),
**response_analytics_attributes(response),
**response_analytics_attributes(response_body),
exception_class: err.class.to_s,
exception_message: err.message,
reason: 'Request exception',
Expand Down Expand Up @@ -231,7 +240,7 @@ def handle_unsupported_id_type(enrollment, response)
enrollment.update(status: :failed)
end

def handle_expired_status_update(enrollment, response)
def handle_expired_status_update(enrollment, response, response_message)
enrollment_outcomes[:enrollments_expired] += 1
analytics(user: enrollment.user).idv_in_person_usps_proofing_results_job_enrollment_updated(
**enrollment_analytics_attributes(enrollment, complete: true),
Expand Down Expand Up @@ -259,6 +268,31 @@ def handle_expired_status_update(enrollment, response)
)
enrollment.update(deadline_passed_sent: true)
end

# check for an unexpected number of days until expiration
Copy link
Contributor

Choose a reason for hiding this comment

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

TY for this helpful comment. 🙏🏻

match = response_message&.match(IPP_EXPIRED_ERROR_MESSAGE)
expired_after_days = match && match[:days]
if expired_after_days.present? &&
expired_after_days.to_i != IdentityConfig.store.in_person_enrollment_validity_in_days
handle_unexpected_response(
enrollment,
response_message,
reason: 'Unexpected number of days before enrollment expired',
cancel: false,
)
end
end

def handle_unexpected_response(enrollment, response_message, reason:, cancel: true)
enrollment.cancelled! if cancel

analytics(user: enrollment.user).
idv_in_person_usps_proofing_results_job_unexpected_response(
enrollment_code: enrollment.enrollment_code,
enrollment_id: enrollment.id,
response_message: response_message,
reason: reason,
)
end
Comment on lines +286 to 296
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have a specific plan for how we can be notified of these issues and respond?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, not yet. I'm going to confirm that they work in lower environments, then I'll probably write up a ticket for alerting.


def handle_failed_status(enrollment, response)
Expand Down
24 changes: 23 additions & 1 deletion app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3183,7 +3183,7 @@ def idv_in_person_usps_proofing_results_job_deadline_passed_email_initiated(
)
end

# Tracks exceptions that are raised when initiating deadline email in GetUspsproofingResultsJob
# Tracks exceptions that are raised when initiating deadline email in GetUspsProofingResultsJob
# @param [String] enrollment_id
# @param [String] exception_class
# @param [String] exception_message
Expand Down Expand Up @@ -3275,6 +3275,28 @@ def idv_in_person_email_reminder_job_email_initiated(
)
end

# Tracks unexpected responses from the USPS API
# @param [String] enrollment_code
# @param [String] enrollment_id
# @param [String] response_message
# @param [String] reason why was this error unexpected?
def idv_in_person_usps_proofing_results_job_unexpected_response(
enrollment_code:,
enrollment_id:,
response_message:,
reason:,
**extra
)
track_event(
'GetUspsProofingResultsJob: Unexpected response received',
enrollment_code: enrollment_code,
enrollment_id: enrollment_id,
response_message: response_message,
reason: reason,
**extra,
)
end

# Tracks users visiting the recovery options page
def account_reset_recovery_options_visit
track_event('Account Reset: Recovery Options Visited')
Expand Down
12 changes: 12 additions & 0 deletions app/services/usps_in_person_proofing/mock/fixtures.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ def self.request_expired_proofing_results_response
load_response_fixture('request_expired_proofing_results_response.json')
end

def self.request_unexpected_expired_proofing_results_response
load_response_fixture('request_unexpected_expired_proofing_results_response.json')
end

def self.request_unexpected_invalid_applicant_response
load_response_fixture('request_unexpected_invalid_applicant_response.json')
end

def self.request_unexpected_invalid_enrollment_code_response
load_response_fixture('request_unexpected_invalid_enrollment_code_response.json')
end

def self.request_no_post_office_proofing_results_response
load_response_fixture('request_no_post_office_proofing_results_response.json')
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"responseMessage": "More than 4 days have passed since opt-in to IPP"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"responseMessage": "Applicant 123456789abcdefghi does not exist"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"responseMessage": "Enrollment code 1234567890123456 does not exist"
}
75 changes: 75 additions & 0 deletions spec/jobs/get_usps_proofing_results_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,81 @@
end
end

context 'when an enrollment expires unexpectedly' do
before(:each) do
stub_request_unexpected_expired_proofing_results
end

it_behaves_like(
'enrollment_with_a_status_update',
passed: false,
status: 'expired',
response_json: UspsInPersonProofing::Mock::Fixtures.
request_unexpected_expired_proofing_results_response,
)

it 'logs that the enrollment expired unexpectedly' do
allow(IdentityConfig.store).to(
receive(:in_person_enrollment_validity_in_days).and_return(30),
)
job.perform(Time.zone.now)

expect(job_analytics).to have_logged_event(
'GetUspsProofingResultsJob: Enrollment status updated',
hash_including(reason: 'Enrollment has expired'),
Copy link
Contributor

Choose a reason for hiding this comment

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

hash_including, TIL!

)

expect(job_analytics).to have_logged_event(
'GetUspsProofingResultsJob: Unexpected response received',
hash_including(reason: 'Unexpected number of days before enrollment expired'),
)
end
end

context 'when an enrollment is reported as invalid' do
context 'when an enrollment code is invalid' do
# this enrollment code is hardcoded into the fixture
# request_unexpected_invalid_enrollment_code_response.json
let(:pending_enrollment) do
create(:in_person_enrollment, :pending, enrollment_code: '1234567890123456')
end
before(:each) do
stub_request_unexpected_invalid_enrollment_code
end

it 'cancels the enrollment and logs that it was invalid' do
job.perform(Time.zone.now)

expect(pending_enrollment.reload.cancelled?).to be_truthy
expect(job_analytics).to have_logged_event(
'GetUspsProofingResultsJob: Unexpected response received',
hash_including(reason: 'Invalid enrollment code'),
)
end
end

context 'when a unique id is invalid' do
# this unique id is hardcoded into the fixture
# request_unexpected_invalid_applicant_response.json
let(:pending_enrollment) do
create(:in_person_enrollment, :pending, unique_id: '123456789abcdefghi')
end
before(:each) do
stub_request_unexpected_invalid_applicant
end

it 'cancels the enrollment and logs that it was invalid' do
job.perform(Time.zone.now)

expect(pending_enrollment.reload.cancelled?).to be_truthy
expect(job_analytics).to have_logged_event(
'GetUspsProofingResultsJob: Unexpected response received',
hash_including(reason: 'Invalid applicant unique id'),
)
end
end
end

context 'when USPS returns a non-hash response' do
before(:each) do
stub_request_proofing_results_with_responses({})
Expand Down
45 changes: 45 additions & 0 deletions spec/support/usps_ipp_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,51 @@ def request_expired_proofing_results_args
}
end

def stub_request_unexpected_expired_proofing_results
stub_request(:post, %r{/ivs-ippaas-api/IPPRest/resources/rest/getProofingResults}).to_return(
**request_unexpected_expired_proofing_results_args,
)
end

def request_unexpected_expired_proofing_results_args
{
status: 400,
body: UspsInPersonProofing::Mock::Fixtures.
request_unexpected_expired_proofing_results_response,
headers: { 'content-type' => 'application/json' },
}
end

def stub_request_unexpected_invalid_applicant
stub_request(:post, %r{/ivs-ippaas-api/IPPRest/resources/rest/getProofingResults}).to_return(
**request_unexpected_invalid_applicant_args,
)
end

def request_unexpected_invalid_applicant_args
{
status: 400,
body: UspsInPersonProofing::Mock::Fixtures.
request_unexpected_invalid_applicant_response,
headers: { 'content-type' => 'application/json' },
}
end

def stub_request_unexpected_invalid_enrollment_code
stub_request(:post, %r{/ivs-ippaas-api/IPPRest/resources/rest/getProofingResults}).to_return(
**request_unexpected_invalid_enrollment_code_args,
)
end

def request_unexpected_invalid_enrollment_code_args
{
status: 400,
body: UspsInPersonProofing::Mock::Fixtures.
request_unexpected_invalid_enrollment_code_response,
headers: { 'content-type' => 'application/json' },
}
end

def stub_request_failed_proofing_results
stub_request(:post, %r{/ivs-ippaas-api/IPPRest/resources/rest/getProofingResults}).to_return(
**request_failed_proofing_results_args,
Expand Down