Repository: mozmeao/basket

Issue:
AMO <> Salesforce Expanded Integration Phase 1
Here's what's needed for phase 1 of the expanded amo integration. 

**Field Mapping:** https://docs.google.com/spreadsheets/d/1ejdszPXF7WIX284UTpXM9Fs2PonTRycGQwEtrkkRT2A/edit?usp=sharing

**Action/Outcome:** https://docs.google.com/document/d/1PIBgZmxbfI-LZlSkB0j4N3wUeJFb2v1JJos8QPHnNSk/edit?usp=sharing

Thank you!


Discussion:
@mozbrandon do you know how different this is from what was discussed the first time in mozilla/addons-server#148 and the work done in mozilla/addons-server#223 ?

Resolving patch:
diff --git a/basket/news/backends/sfdc.py b/basket/news/backends/sfdc.py
--- a/basket/news/backends/sfdc.py
+++ b/basket/news/backends/sfdc.py
@@ -75,6 +75,7 @@ def cast_lower(value):
     'amo_last_login': 'AMO_Last_Login__c',
     'amo_location': 'AMO_Location__c',
     'amo_homepage': 'AMO_Homepage_URL__c',
+    'amo_deleted': 'AMO_Account_Deleted__c',
     'payee_id': 'PMT_Cust_Id__c',
     'fxa_id': 'FxA_Id__c',
     'fxa_deleted': 'FxA_Account_Deleted__c',
diff --git a/basket/news/tasks.py b/basket/news/tasks.py
--- a/basket/news/tasks.py
+++ b/basket/news/tasks.py
@@ -1322,33 +1322,41 @@ def upsert_amo_user_data(data):
     :param data: dict of amo user data
     :return: dict of SFDC contact data
     """
-    email = data.pop('email')
+    fxa_id = data.pop('fxa_id')
     amo_id = data.pop('id')
+
+    user = get_user_data(amo_id=amo_id, extra_fields=['id', 'amo_id', 'fxa_id'])
+    if not user:
+        # Try to find user with fxa_id
+        user = get_user_data(fxa_id=fxa_id, extra_fields=['id', 'amo_id', 'fxa_id'])
+
+    if not user:
+        # Cannot find user with FxA ID or AMO ID, ignore the update
+        return None
+
     amo_deleted = data.pop('deleted', False)
     amo_data = {f'amo_{k}': v for k, v in data.items() if v}
-    amo_data['amo_user'] = not amo_deleted
-    user = get_user_data(amo_id=amo_id, extra_fields=['id', 'amo_id'])
-    if user:
-        sfdc.update(user, amo_data)
-        return user
-
-    # include the ID in update or add since we couldn't find
-    # the user with this ID above
+    amo_data['amo_user'] = True
     amo_data['amo_id'] = amo_id
-    user = get_user_data(email=email, extra_fields=['id'])
-    if user:
-        sfdc.update(user, amo_data)
-        # need amo_id for linking addons and authors
-        user['amo_id'] = amo_id
-        return user
-
-    amo_data['email'] = email
-    amo_data['source_url'] = 'https://addons.mozilla.org/'
-    # returns only the new user ID in a dict, but that will work
-    # when passed to e.g. `sfdc.update()`
-    user = sfdc.add(amo_data)
-    # need amo_id for linking addons and authors
-    user['amo_id'] = amo_id
+
+    if amo_deleted:
+        if fxa_id is None:
+            # Deleted user, we don't want do keep linking that AMO account with
+            # that FxA account in Salesforce, they might re-create a new AMO
+            # account from their previous FxA account.
+            amo_data['amo_id'] = None
+            amo_data['amo_deleted'] = amo_deleted
+        if user['fxa_id'] == fxa_id:
+            # Banned AMO user. They can't re-use that FxA account to create
+            # another AMO account so we can leave the link.
+            amo_data['amo_deleted'] = amo_deleted
+    else:
+        if fxa_id is None:
+            # User takeover protection, we don't want do keep linking that AMO
+            # account with that FxA account in Salesforce
+            amo_data['amo_id'] = None
+
+    sfdc.update(user, amo_data)
     return user
 
 
@@ -1375,6 +1383,7 @@ def amo_sync_addon(data):
         'AMO_Update__c': data['last_updated'],
         'Average_Daily_Users__c': data['average_daily_users'],
         'Dev_Disabled__c': 'Yes' if data['is_disabled'] else 'No',
+        'AMO_Recommended__c': data['is_recommended'],
     }
     # check for possible None or empty values
     if data['name']:


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
@@ -1623,7 +1623,7 @@ def setUp(self):
             'guid': '{85ee4a2a-51b6-4f5e-a99c-6d9abcf6782d}',
             'id': 35896,
             'is_disabled': False,
-            'is_recommended': False,
+            'is_recommended': True,
             'last_updated': '2019-06-26T11:38:13Z',
             'latest_unlisted_version': {
                 'compatibility': {
@@ -1679,6 +1679,7 @@ def test_update_addon(self, uaud_mock, sfdc_mock):
             'AMO_Update__c': '2019-06-26T11:38:13Z',
             'Average_Daily_Users__c': 0,
             'Dev_Disabled__c': 'No',
+            'AMO_Recommended__c': True,
             'Name': 'Ibird Jelewt Boartrica',
         })
         sfdc_mock.dev_addon.upsert.assert_has_calls([
@@ -1711,6 +1712,7 @@ def test_null_values(self, uaud_mock, sfdc_mock):
             'AMO_Update__c': '2019-06-26T11:38:13Z',
             'Average_Daily_Users__c': 0,
             'Dev_Disabled__c': 'No',
+            'AMO_Recommended__c': True,
             'Name': 'Ibird Jelewt Boartrica',
             'AMO_Current_Version__c': '',
             'AMO_Current_Version_Unlisted__c': '',
@@ -1734,7 +1736,7 @@ def setUp(self):
         self.amo_data = {
             'id': 1234,
             'display_name': 'His Dudeness',
-            'email': 'dude@example.com',
+            'fxa_id': 'fxa_id_of_dude',
             'homepage': 'https://elduder.io',
             'last_login': '2019-08-06T10:39:44Z',
             'location': 'California, USA, Earth',
@@ -1743,7 +1745,7 @@ def setUp(self):
         self.user_data = {
             'id': 'A1234',
             'amo_id': 1234,
-            'email': 'the-dude@example.com'
+            'fxa_id': 'fxa_id_of_dude'
         }
 
     def test_existing_user_with_amo_id(self, gud_mock, sfdc_mock):
@@ -1751,6 +1753,7 @@ def test_existing_user_with_amo_id(self, gud_mock, sfdc_mock):
         amo_sync_user(self.amo_data)
         # does not include email or amo_id
         sfdc_mock.update.assert_called_with(self.user_data, {
+            'amo_id': 1234,
             'amo_display_name': 'His Dudeness',
             'amo_homepage': 'https://elduder.io',
             'amo_last_login': '2019-08-06T10:39:44Z',
@@ -1773,45 +1776,50 @@ def test_existing_user_no_amo_id(self, gud_mock, sfdc_mock):
 
     def test_new_user(self, gud_mock, sfdc_mock):
         gud_mock.return_value = None
-        amo_sync_user(self.amo_data)
+        user = amo_sync_user(self.amo_data)
         sfdc_mock.update.assert_not_called()
-        # includes email and amo_id
-        sfdc_mock.add.assert_called_with({
-            'email': 'dude@example.com',
-            'amo_id': 1234,
+        assert user is None
+
+    def test_deleted_user_matching_fxa_id(self, gud_mock, sfdc_mock):
+        self.amo_data['deleted'] = True
+        gud_mock.return_value = self.user_data
+        amo_sync_user(self.amo_data)
+        sfdc_mock.update.assert_called_with(self.user_data, {
             'amo_display_name': 'His Dudeness',
             'amo_homepage': 'https://elduder.io',
             'amo_last_login': '2019-08-06T10:39:44Z',
             'amo_location': 'California, USA, Earth',
-            'source_url': 'https://addons.mozilla.org/',
             'amo_user': True,
+            'amo_deleted': True,
+            'amo_id': 1234,
         })
 
-    def test_deleted_user(self, gud_mock, sfdc_mock):
+    def test_deleted_user_fxa_id_is_None(self, gud_mock, sfdc_mock):
         self.amo_data['deleted'] = True
+        self.amo_data['fxa_id'] = None
         gud_mock.return_value = self.user_data
         amo_sync_user(self.amo_data)
-        # does not include email or amo_id
         sfdc_mock.update.assert_called_with(self.user_data, {
             'amo_display_name': 'His Dudeness',
             'amo_homepage': 'https://elduder.io',
             'amo_last_login': '2019-08-06T10:39:44Z',
             'amo_location': 'California, USA, Earth',
-            'amo_user': False,
+            'amo_user': True,
+            'amo_deleted': True,
+            'amo_id': None
         })
 
-    def test_null_values(self, gud_mock, sfdc_mock):
-        gud_mock.return_value = None
-        self.amo_data['display_name'] = None
-        self.amo_data['last_login'] = None
-        self.amo_data['location'] = None
+    def test_not_deleted_user_fxa_id_is_None(self, gud_mock, sfdc_mock):
+        self.amo_data['fxa_id'] = None
+        gud_mock.return_value = self.user_data
         amo_sync_user(self.amo_data)
-        sfdc_mock.add.assert_called_with({
-            'email': 'dude@example.com',
-            'amo_id': 1234,
+        sfdc_mock.update.assert_called_with(self.user_data, {
+            'amo_display_name': 'His Dudeness',
             'amo_homepage': 'https://elduder.io',
-            'source_url': 'https://addons.mozilla.org/',
+            'amo_last_login': '2019-08-06T10:39:44Z',
+            'amo_location': 'California, USA, Earth',
             'amo_user': True,
+            'amo_id': None,
         })