Skip to content

Commit

Permalink
Add test that hits race condition inside _get_or_create_from_stripe_o…
Browse files Browse the repository at this point in the history
…bject

Without dj-stripe#877 this hits: django.db.transaction.TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
  • Loading branch information
therefromhere committed Jun 11, 2019
1 parent 580fe81 commit 494dac4
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 3 deletions.
3 changes: 2 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ History

This is a bugfix-only version:

- When processing stripe event, wrap create object call in transaction (#877)
- In ``_get_or_create_from_stripe_object``, wrap create ``_create_from_stripe_object`` in transaction,
fixes ``TransactionManagementError`` on race condition in webhook processing (#877/#903).

2.0.2 (2019-06-09)
------------------
Expand Down
60 changes: 58 additions & 2 deletions tests/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
from stripe.error import StripeError

from djstripe import webhooks
from djstripe.models import Event
from djstripe.models import Event, Transfer

from . import FAKE_CUSTOMER, FAKE_EVENT_TRANSFER_CREATED
from . import FAKE_CUSTOMER, FAKE_EVENT_TRANSFER_CREATED, FAKE_TRANSFER


class EventTest(TestCase):
Expand Down Expand Up @@ -103,6 +103,34 @@ def test_process_event_exists(
# Make sure the existing event was returned.
self.assertEqual(mock_objects.filter.return_value.first.return_value, result)

@patch("djstripe.models.Event.invoke_webhook_handlers", autospec=True)
def test_process_event_failure_rolls_back(self, invoke_webhook_handlers_mock):
"""Test that process event rolls back event creation on error
"""

class HandlerException(Exception):
pass

invoke_webhook_handlers_mock.side_effect = HandlerException
real_create_from_stripe_object = Event._create_from_stripe_object

def side_effect(*args, **kwargs):
return real_create_from_stripe_object(*args, **kwargs)

event_data = deepcopy(FAKE_EVENT_TRANSFER_CREATED)

self.assertFalse(Event.objects.filter(id=FAKE_EVENT_TRANSFER_CREATED["id"]).exists())

with self.assertRaises(HandlerException), patch(
"djstripe.models.Event._create_from_stripe_object",
side_effect=side_effect,
autospec=True,
) as create_from_stripe_object_mock:
Event.process(data=event_data)

create_from_stripe_object_mock.assert_called_once_with(event_data)
self.assertFalse(Event.objects.filter(id=FAKE_EVENT_TRANSFER_CREATED["id"]).exists())

#
# Helpers
#
Expand All @@ -113,3 +141,31 @@ def _create_event(self, event_data, event_retrieve_mock):
event_retrieve_mock.return_value = event_data
event = Event.sync_from_stripe_data(event_data)
return event


class EventRaceConditionTest(TestCase):
@patch("stripe.Transfer.retrieve", return_value=deepcopy(FAKE_TRANSFER), autospec=True)
def test_process_event_race_condition(self, transfer_retrieve_mock):
transfer = Transfer.sync_from_stripe_data(deepcopy(FAKE_TRANSFER))
transfer_retrieve_mock.reset_mock()
event_data = deepcopy(FAKE_EVENT_TRANSFER_CREATED)

# emulate the race condition in _get_or_create_from_stripe_object where an object is created
# by a different request during the call
#
# Sequence of events:
# 1) first Transfer.stripe_objects.get fails with DoesNotExist (due to it not existing in reality,
# but due to our side_effect in the test)
# 2) object is really created by a different request in reality
# 3) Transfer._create_from_stripe_object fails with IntegrityError due to duplicate id
# 4) second Transfer.stripe_objects.get succeeds (due to being created by step 2 in reality, due to
# side effect in the test)
side_effect = [Transfer.DoesNotExist(), transfer]

with patch(
"djstripe.models.Transfer.stripe_objects.get", side_effect=side_effect, autospec=True
) as transfer_objects_get_mock:
Event.process(event_data)

self.assertEqual(transfer_objects_get_mock.call_count, 2)
self.assertEqual(transfer_retrieve_mock.call_count, 1)

0 comments on commit 494dac4

Please sign in to comment.