Memberships
This commit is contained in:
Executable
+55
@@ -0,0 +1,55 @@
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from unittest import skipUnless
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.db import close_old_connections, connection, transaction
|
||||
from django.test import TransactionTestCase, override_settings
|
||||
|
||||
from members.billing import sync_account
|
||||
from members.models import Account, ManagedGroup
|
||||
|
||||
|
||||
@skipUnless(connection.vendor == "postgresql", "Requires real PostgreSQL row locks")
|
||||
class PostgreSQLLockTests(TransactionTestCase):
|
||||
def setUp(self):
|
||||
self.account = Account.objects.create(uid="lock-test", email="lock@example.org", ldap_dn="uid=lock-test,dc=example,dc=org", ldap_uuid="lock-test-uuid")
|
||||
|
||||
@override_settings(MEMBERSHIP_TIERS={})
|
||||
def test_checkout_account_lock_excludes_reconciliation(self):
|
||||
attempted = threading.Event()
|
||||
acquired = threading.Event()
|
||||
|
||||
def worker():
|
||||
close_old_connections()
|
||||
attempted.set()
|
||||
try:
|
||||
sync_account(self.account.pk)
|
||||
finally:
|
||||
close_old_connections()
|
||||
|
||||
def refresh(member):
|
||||
acquired.set()
|
||||
|
||||
with patch("members.billing.refresh_billing", side_effect=refresh):
|
||||
with ThreadPoolExecutor(max_workers=1) as pool:
|
||||
with transaction.atomic():
|
||||
# Checkout uses this identical row lock before Stripe reads.
|
||||
Account.objects.select_for_update().get(pk=self.account.pk)
|
||||
future = pool.submit(worker)
|
||||
self.assertTrue(attempted.wait(5))
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT pg_backend_pid()")
|
||||
# The worker cannot reach billing while Checkout owns lock.
|
||||
self.assertFalse(acquired.is_set())
|
||||
future.result(timeout=10)
|
||||
self.assertTrue(acquired.is_set())
|
||||
|
||||
@override_settings(MEMBERSHIP_TIERS={"test": {"group_dn": "cn=paid,dc=example,dc=org"}})
|
||||
def test_intent_commits_before_external_grant_failure(self):
|
||||
with patch("members.billing.reconcile_groups", side_effect=OSError("directory unavailable")):
|
||||
with self.assertRaises(OSError):
|
||||
sync_account(self.account.pk)
|
||||
# A later failed external side effect rolls back its own transaction,
|
||||
# not the previously committed record of the managed group.
|
||||
self.assertEqual(ManagedGroup.objects.get(account=self.account).dn, "cn=paid,dc=example,dc=org")
|
||||
Reference in New Issue
Block a user