Repository: mozmeao/basket

Issue:
Lookup new SubHub users by FxA ID first
We currently encounter errors when a user comes in via SubHub with an email address and FxA ID, but another contact in Salesforce already has that FxA ID. This can happen due to the user changing their FxA email address at some point. To combat this @mozbrandon, @andrewmozilla, and I discussed solutions and decided on the following:

Look up users by FxA ID first. If a record is found:

* If the email matches the one FxA sent us, then we've found the right record and can continue on.
* If the emails don't match then we update the contact found above to have the `FxA_Id__c` field contain `DUPE:<fxa_id>` and set the `FxA_Account_Deleted__c` field to `True`. We then need to look up the FxA email to see if there's a contact for that email.
  * If there is a contact for the FxA email then fetch it and update it to have the correct FxA ID.
  * If there is not a contact for the FxA email then create one

I think that's it. @andrewmozilla please correct me if I'm wrong.


Discussion:
This sounds good PMac! 
Last comment, I believe we discussed that this change would only apply to Subscription Services (purchase/customer) events. Is that true?
That's correct. Thanks!

Resolving patch:
diff --git a/basket/news/tasks.py b/basket/news/tasks.py
--- a/basket/news/tasks.py
+++ b/basket/news/tasks.py
@@ -741,10 +741,29 @@ def process_subhub_event_customer_created(data):
         'payee_id': data['customer_id']
     }
 
-    user_data = get_user_data(email=data['email'])
+    user_data = None
+    # try getting user data with the fxa_id first
+    user_data_fxa = get_user_data(fxa_id=contact_data['fxa_id'],
+                                  extra_fields=['id'])
+    if user_data_fxa:
+        # if the email matches what we got from subhub, which got it from fxa, we're good
+        if user_data_fxa['email'] == data['email']:
+            user_data = user_data_fxa
+        # otherwise we've gotta make sure this one doesn't interfere with us updating or creating
+        # the one with the right email address below
+        else:
+            statsd.incr('news.tasks.process_subhub_event.customer_created.fxa_id_dupe')
+            sfdc.update(user_data_fxa, {
+                'fxa_id': f"DUPE:{contact_data['fxa_id']}",
+                'fxa_deleted': True,
+            })
+
+    # if we still don't have user data try again with email this time
+    if not user_data:
+        user_data = get_user_data(email=data['email'], extra_fields=['id'])
 
-    # if user was found in sfdc, see if we should update their name(s)
     if user_data:
+        # if user was found in sfdc, see if we should update their name(s)
         # if current last name is '_', update it
         if user_data['last_name'] == '_':
             contact_data['last_name'] = last


Test patch:
diff --git a/basket/news/tests/test_tasks.py b/basket/news/tests/test_tasks.py
--- a/basket/news/tests/test_tasks.py
+++ b/basket/news/tests/test_tasks.py
@@ -386,12 +386,15 @@ def test_credit_card_expiring(self, sfmc_mock, sfdc_mock, gud_mock):
             'dude@example.com', 'dude@example.com')
 
     def test_customer_created_customer_found(self, sfdc_mock, gud_mock):
+        """
+        Contact found by email only
+        """
         user_data = {
             'first_name': 'Jeffrey',
             'last_name': '_',
         }
 
-        gud_mock.return_value = user_data
+        gud_mock.side_effect = [None, user_data]
 
         process_subhub_event_customer_created({
             'name': 'Jeffrey Lebowski',
@@ -407,17 +410,110 @@ def test_customer_created_customer_found(self, sfdc_mock, gud_mock):
         })
 
     def test_customer_created_customer_not_found(self, sfdc_mock, gud_mock):
+        """
+        No contact found at all
+        """
         gud_mock.return_value = None
 
         process_subhub_event_customer_created({
             'name': 'Walter Sobchak',
-            'email': 'water@thedude.io',
+            'email': 'walter@thedude.io',
             'user_id': '1234',
             'customer_id': 'cus_1234',
         })
 
         sfdc_mock.update.assert_not_called()
 
+        sfdc_mock.add.assert_called_with({
+            'fxa_id': '1234',
+            'payee_id': 'cus_1234',
+            'first_name': 'Walter',
+            'last_name': 'Sobchak',
+            'email': 'walter@thedude.io',
+        })
+
+    def test_customer_created_customer_fxa_found_match(self, sfdc_mock, gud_mock):
+        """
+        Contact found by FxA_ID and email matches
+        """
+        user_data = {
+            'first_name': 'Jeffrey',
+            'last_name': '_',
+            'email': 'walter@thedude.io'
+        }
+        gud_mock.return_value = user_data
+
+        process_subhub_event_customer_created({
+            'name': 'Walter Sobchak',
+            'email': 'walter@thedude.io',
+            'user_id': '1234',
+            'customer_id': 'cus_1234',
+        })
+
+        gud_mock.assert_called_once_with(fxa_id='1234', extra_fields=['id'])
+        sfdc_mock.update.assert_called_once_with(user_data, {
+            'fxa_id': '1234',
+            'payee_id': 'cus_1234',
+            'last_name': 'Sobchak',
+        })
+
+    def test_customer_created_customer_fxa_found_no_match_yes_other(self, sfdc_mock, gud_mock):
+        """
+        Contact found by FxA_ID and email does not match, and other found by email
+        """
+        user_data_fxa = {
+            'first_name': 'Jeffrey',
+            'last_name': '_',
+            'email': 'dude@thedude.io'
+        }
+        user_data = {
+            'first_name': 'Jeffrey',
+            'last_name': '_',
+            'email': 'walter@thedude.io'
+        }
+        gud_mock.side_effect = [user_data_fxa, user_data]
+
+        process_subhub_event_customer_created({
+            'name': 'Walter Sobchak',
+            'email': 'water@thedude.io',
+            'user_id': '1234',
+            'customer_id': 'cus_1234',
+        })
+
+        sfdc_mock.update.assert_has_calls([
+            call(user_data_fxa, {
+                'fxa_id': 'DUPE:1234',
+                'fxa_deleted': True,
+            }),
+            call(user_data, {
+                'fxa_id': '1234',
+                'payee_id': 'cus_1234',
+                'last_name': 'Sobchak',
+            })
+        ])
+
+    def test_customer_created_customer_fxa_found_no_match_no_other(self, sfdc_mock, gud_mock):
+        """
+        Contact found by FxA_ID and email does not match, and no other found by email
+        """
+        user_data_fxa = {
+            'first_name': 'Jeffrey',
+            'last_name': '_',
+            'email': 'dude@thedude.io'
+        }
+        gud_mock.side_effect = [user_data_fxa, None]
+
+        process_subhub_event_customer_created({
+            'name': 'Walter Sobchak',
+            'email': 'water@thedude.io',
+            'user_id': '1234',
+            'customer_id': 'cus_1234',
+        })
+
+        sfdc_mock.update.assert_called_once_with(user_data_fxa, {
+            'fxa_id': 'DUPE:1234',
+            'fxa_deleted': True,
+        })
         sfdc_mock.add.assert_called_with({
             'fxa_id': '1234',
             'payee_id': 'cus_1234',