Skip to content

LG-11997 | Conditionally marks users as fraudulent#9928

Merged
n1zyy merged 22 commits intomainfrom
mattw/LG-11997
Jan 29, 2024
Merged

LG-11997 | Conditionally marks users as fraudulent#9928
n1zyy merged 22 commits intomainfrom
mattw/LG-11997

Conversation

@n1zyy
Copy link
Contributor

@n1zyy n1zyy commented Jan 16, 2024

🎫 Ticket

Link to the relevant ticket:
LG-11997

🛠 Summary of changes

If a user has a fraud_pending_reason from TMX, mark them as fraud_review_pending_at when we get a response back from the USPS.

📜 Testing Plan

It may make sense to slow-roll this until Keith's PR for LG-12016 is ready, because his story will come first for a user.

n1zyy added 2 commits January 16, 2024 17:04
If a user has a fraud_pending_reason, mark them as fraud_review_pending_at
when we get a response.

changelog: Internal, IPP, Mark users as fraud_review_pending_at if appropriate
response = nil

response = proofer.request_proofing_results(
enrollment.unique_id, enrollment.enrollment_code
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The assignment on line 102 was useless since we unconditionally assign it here. My editor happened to highlight it while I was working my way through here.


# MW: What else do we want to log here?
def handle_fraud_review_pending(enrollment)
enrollment.profile&.deactivate_for_fraud_review
Copy link
Contributor Author

Choose a reason for hiding this comment

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

.profile& based on LG-12020. It's rare and weird for profile to be missing, but it can happen.

Copy link
Contributor

@svalexander svalexander Jan 18, 2024

Choose a reason for hiding this comment

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

sidenote, do we want to update other places where we're accessing properties from profile? (in a future ticket)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm conflicted on this. It's a good idea, but the case in LG-12020 is currently the only one that has ever broken. I'm not sure if I want to create a ticket that will just rot in the backlog.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, so in following this code a bit more, it seems like if the profile doesn't exist we wouldn't fail fraud_review_pending?, and then the application would crash at line 382, when we try to call activate_after_passing_in_person on the profile. So it seems to me that we'd want to do the safe navigation operator in lines 382 & 421 (which you already have), and you could probably remove the operator in the handle_fraud_review_pending.

But I feel like if the profile is missing we should bounce outta here sooner, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, nice catch!

I think I added this one when I had LG-12020 still on my mind, and then failed to consider it elsewhere. Maybe I should add a quick test case for this too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I deleted my last comment, which turns out to be suggesting a ton of work for little benefit. I updated the instances you referred to, and removed it here. It is indeed impossible to reach this line without a profile.

reason: 'Successful status update',
job_name: self.class.name,
)
enrollment.profile.activate_after_passing_in_person
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This, and 373-380, are now conditional on the user not being fraud_pending?, so this got moved down. We still want the enrollment update below, though.

# send SMS and email
send_enrollment_status_sms_notification(enrollment: enrollment)
# MW: I _think_ this is desired behavior?
send_failed_email(enrollment.user, enrollment)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea is that if you get a failure proofing, we'll just send you that, and the fact that we also had you flagged as fraudulent is kind of irrelevant.

return
end

# We want to deactivate them regardless of status:
Copy link
Contributor Author

@n1zyy n1zyy Jan 16, 2024

Choose a reason for hiding this comment

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

I.e., regardless of response['status']. We'll always take this step if they're so flagged, and then we just needed to tweak the success case to not fully activate them.

@n1zyy
Copy link
Contributor Author

n1zyy commented Jan 16, 2024

FIXME: This should respect the feature flag being added in LG-12016.

profile = pending_enrollment.profile
expect(profile).to be_fraud_review_pending
expect(profile).not_to be_active
end
Copy link
Contributor

Choose a reason for hiding this comment

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

The AC may have changed. You and I talked about exiting behavior vs what was in the issue. I would have expected fraud_review_pending to be null and fraud_review_pending_at to have a timestamp on in. I may have missed the ask here and/or the requirements/AC have changed upon diving into the code and discovering existing processes. If they have changed- we can update the AC in the ticket.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for flagging this. It did change; there is now a link in the Jira ticket to the Slack thread where it came up. It turns out that the existing pattern is to leave fraud_pending_reason in place.

I tried to be clever with expect(profile).to be_fraud_review_pending?, which exercises this method:

  def fraud_review_pending?
    fraud_review_pending_at.present?
  end

But in hindsight I think it might be clearer if I just did expect(profile.fraud_review_pending_at).to eq (Time.zone.now) (or similar), rather than relying on a magic matcher + non-obvious helper method in Profile.

**extra
)
track_event(
:idv_in_person_usps_proofing_results_job_user_sent_to_fraud_review,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TIL that we are moving to this model for new events, vs. a string, and it must match the method name. This was discussed as a previous eng huddle, but apparently one where I wasn't present.

profile.deactivate_for_gpo_verification if gpo_verification_needed
if fraud_pending_reason.present? && !gpo_verification_needed

if fraud_pending_reason.present? && !gpo_verification_needed && !in_person_verification_needed
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@svalexander This is the fix you helped catch yesterday! 🥳 I also updated the ProfileMakerSpec to cover this. I think it would have taken way longer for me to find this if you hadn't caught it; thanks!

it 'is not fraud_review_pending?' do
expect(profile.fraud_review_pending?).to eq(false)
end
end
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These test my change to the ProfileMaker. Without the change, but with this test, the GPO verification case passes, but the IPP one did not.


# MW: What else do we want to log here?
def handle_fraud_review_pending(enrollment)
enrollment.profile&.deactivate_for_fraud_review
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm conflicted on this. It's a good idea, but the case in LG-12020 is currently the only one that has ever broken. I'm not sure if I want to create a ticket that will just rot in the backlog.

in_person_email_reminder_final_benchmark_in_days: 1
in_person_email_reminder_late_benchmark_in_days: 4
in_person_proofing_enabled: false
in_person_proofing_enforce_tmx: false
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Global default is false

hmac_fingerprinter_key_queue: '["11111111111111111111111111111111", "22222222222222222222222222222222"]'
identity_pki_local_dev: true
in_person_proofing_enabled: true
in_person_proofing_enforce_tmx: true
Copy link
Contributor Author

Choose a reason for hiding this comment

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

But it's true in dev

stub_request_passed_proofing_results
end

it 'updates the enrollment' do
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one example happens whether the flag is true or false, because it happens before we look at fraud statuses. It feels a little lonely out here though.

@n1zyy
Copy link
Contributor Author

n1zyy commented Jan 26, 2024

I added a commit which makes FakeAnalytics add user_id if a User object is passed into the constructor, like the real track_event it replaces.

With that, this test passes:

            it 'logs a user_sent_to_fraud_review analytics event' do
              job.perform(Time.zone.now)

              expect(job_analytics).to have_logged_event(
                :idv_in_person_usps_proofing_results_job_user_sent_to_fraud_review,
                hash_including(
                  enrollment_code: pending_enrollment.enrollment_code,
                  user_id: user.uuid,
                ),
              )
            end

However, the change would require updating a ton of existing tests which make assertions about what is logged and aren't expecting user_id to be there. I think I'm going to remove the user_id line from this PR and get it through without it, and then separately add it back in a follow-on PR fixing all the other tests it would break.

n1zyy added 2 commits January 26, 2024 12:51
This reverts commit 952b935.

(This is good stuff IMHO, but it belongs in its own PR.)
Copy link
Contributor

@JackRyan1989 JackRyan1989 left a comment

Choose a reason for hiding this comment

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

Looks real good! Just sending over some comments but willing to approve upon further discussion.


# MW: What else do we want to log here?
def handle_fraud_review_pending(enrollment)
enrollment.profile&.deactivate_for_fraud_review
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, so in following this code a bit more, it seems like if the profile doesn't exist we wouldn't fail fraud_review_pending?, and then the application would crash at line 382, when we try to call activate_after_passing_in_person on the profile. So it seems to me that we'd want to do the safe navigation operator in lines 382 & 421 (which you already have), and you could probably remove the operator in the handle_fraud_review_pending.

But I feel like if the profile is missing we should bounce outta here sooner, right?

end

def handle_fraud_review_pending(enrollment)
return unless IdentityConfig.store.in_person_proofing_enforce_tmx
Copy link
Contributor

Choose a reason for hiding this comment

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

So this is totally a super nit, but I'm wondering if it makes sense to move this config check into it's own method like we do for ipp_enabled on line 65 and then use it on line 432 to determine if we should call handle_fraud_review_pending? I kinda like that pattern of escalating and making more explicit the prerequisites for a method to be called. But also, this is non-blocking I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's exactly the pattern @svalexander took in her PR for LG-12091. I like it!

(Some part of my brain really wants to have the method itself enforce this, but I don't think it's necessary.)

email_type: 'Success',
job_name: self.class.name,
)
unless fraud_result_pending?(enrollment)
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 need to check the config setting here too? Based on your specs I think no, but I'm not totally clear on when fraud_pending_reason is available on the enrollment profile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh! I think we should.

We're not the first ones to use fraud_pending_reason, so the feature flag should toggle whether or not we act on it here. Seems like I need to clean up the test as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

@n1zyy n1zyy left a comment

Choose a reason for hiding this comment

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

@JackRyan1989 Thanks for your thorough review here—you caught a few different things I missed! (And thanks also for pairing earlier on the logging issue I had.)


# MW: What else do we want to log here?
def handle_fraud_review_pending(enrollment)
enrollment.profile&.deactivate_for_fraud_review
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, nice catch!

I think I added this one when I had LG-12020 still on my mind, and then failed to consider it elsewhere. Maybe I should add a quick test case for this too?

end

def handle_fraud_review_pending(enrollment)
return unless IdentityConfig.store.in_person_proofing_enforce_tmx
Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's exactly the pattern @svalexander took in her PR for LG-12091. I like it!

(Some part of my brain really wants to have the method itself enforce this, but I don't think it's necessary.)

email_type: 'Success',
job_name: self.class.name,
)
unless fraud_result_pending?(enrollment)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh! I think we should.

We're not the first ones to use fraud_pending_reason, so the feature flag should toggle whether or not we act on it here. Seems like I need to clean up the test as well.

Copy link
Contributor

@JackRyan1989 JackRyan1989 left a comment

Choose a reason for hiding this comment

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

Lgtm!

@n1zyy n1zyy merged commit e4ff006 into main Jan 29, 2024
@n1zyy n1zyy deleted the mattw/LG-11997 branch January 29, 2024 17:51
jmhooper added a commit that referenced this pull request Jun 11, 2024
…r fraud-pending in-person users

When the identity verification report was built the fraud-review functionality did not apply to in-person pending. This was changed in #9928, but the report was not updated to filter fraud-pending users out of the successfully verified count for in-person events.

This commit modifies the cloudwatch query to filter those users who are not verified from being counted as successfully verified.

[skip changelog]
jmhooper added a commit that referenced this pull request Jun 11, 2024
… to filter fraud-pending in-person users (#10798)

When the identity verification report was built the fraud-review functionality did not apply to in-person pending. This was changed in #9928, but the report was not updated to filter fraud-pending users out of the successfully verified count for in-person events.

This commit modifies the cloudwatch query to filter those users who are not verified from being counted as successfully verified.

[skip changelog]
brandemix pushed a commit to brandemix/18F-identity-idp that referenced this pull request Jun 17, 2024
… to filter fraud-pending in-person users (18F#10798)

When the identity verification report was built the fraud-review functionality did not apply to in-person pending. This was changed in 18F#9928, but the report was not updated to filter fraud-pending users out of the successfully verified count for in-person events.

This commit modifies the cloudwatch query to filter those users who are not verified from being counted as successfully verified.

[skip changelog]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants