Repository: mozmeao/basket

Issue:
Add dupe prevention to donation record creation
We are changing the Opportunity objects to have the transaction ID be unique, so we'll need to catch that exception and then drop the update. We should also add some rate limiting (or locking) to the task on transaction ID so that they can't come in so quickly as to circumvent the unique index on the records.


Discussion:

Resolving patch:
diff --git a/basket/news/tasks.py b/basket/news/tasks.py
--- a/basket/news/tasks.py
+++ b/basket/news/tasks.py
@@ -761,7 +761,7 @@ def process_donation_event(data):
         reason_lost = data['failure_code']
 
     try:
-        # will raise a SalesforceMalformedRequest if not found and will be retried
+        # will raise a SalesforceMalformedRequest if not found
         sfdc.opportunity.update('PMT_Transaction_ID__c/{}'.format(txn_id), {
             'PMT_Type_Lost__c': etype,
             'PMT_Reason_Lost__c': reason_lost,
@@ -865,7 +865,14 @@ def process_donation(data):
         if source_name in data:
             donation[dest_name] = data[source_name]
 
-    sfdc.opportunity.create(donation)
+    try:
+        sfdc.opportunity.create(donation)
+    except sfapi.SalesforceMalformedRequest as e:
+        if e.content and e.content[0].get('errorCode') == 'DUPLICATE_VALUE':
+            # already in the system, ignore
+            pass
+        else:
+            raise
 
 
 @celery_app.task()


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
@@ -6,6 +6,7 @@
 from django.test import TestCase
 from django.test.utils import override_settings
 
+import simple_salesforce as sfapi
 from mock import ANY, Mock, patch
 
 from basket.news.celery import app as celery_app
@@ -207,6 +208,40 @@ def test_donation_data(self, sfdc_mock, gud_mock):
             'Project__c': data['project'],
         })
 
+    def test_donation_silent_failure_on_dupe(self, sfdc_mock, gud_mock):
+        data = self.donate_data.copy()
+        gud_mock.return_value = {
+            'id': '1234',
+            'first_name': 'Jeffery',
+            'last_name': 'Lebowski',
+        }
+        error_content = [{
+            u'errorCode': u'DUPLICATE_VALUE',
+            u'fields': [],
+            u'message': u'duplicate value found: PMT_Transaction_ID__c '
+                        u'duplicates value on record with id: blah-blah',
+        }]
+        exc = sfapi.SalesforceMalformedRequest('url', 400, 'opportunity', error_content)
+        sfdc_mock.opportunity.create.side_effect = exc
+        process_donation(data)
+
+    def test_donation_normal_failure_not_dupe(self, sfdc_mock, gud_mock):
+        data = self.donate_data.copy()
+        gud_mock.return_value = {
+            'id': '1234',
+            'first_name': 'Jeffery',
+            'last_name': 'Lebowski',
+        }
+        error_content = [{
+            u'errorCode': u'OTHER_ERROR',
+            u'fields': [],
+            u'message': u'Some other non-dupe problem',
+        }]
+        exc = sfapi.SalesforceMalformedRequest('url', 400, 'opportunity', error_content)
+        sfdc_mock.opportunity.create.side_effect = exc
+        with self.assertRaises(sfapi.SalesforceMalformedRequest):
+            process_donation(data)
+
 
 @override_settings(TASK_LOCKING_ENABLE=True)
 class TaskDuplicationLockingTests(TestCase):