Files
www/membership/members/tests/test_postgres.py
T
2026-10-23 12:19:40 -04:00

81 lines
4.0 KiB
Python
Executable File

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
from members.models import CryptoInvoice
@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")
@override_settings(SHKEEPER_ENABLED=True, MEMBERSHIP_TIERS={"test": {"group_dn": "cn=paid,dc=example,dc=org"}})
def test_concurrent_crypto_verification_credits_one_month(self):
invoice = CryptoInvoice.objects.create(account=self.account, tier="test", usd_amount="5.00", crypto="BTC", provider_id="61", wallet="test-wallet", crypto_amount="0.0001", status="UNPAID")
data = {"status": "success", "invoices": [{"external_id": f"www-membership-{invoice.pk}", "fiat": "USD", "amount_fiat": "5.00", "balance_fiat": "5.00", "status": "PAID", "txs": [{"addr": "test-wallet", "crypto": "BTC", "status": "CONFIRMED", "txid": "tx-confirmed", "amount": "0.0001"}]}]}
def verify():
close_old_connections()
try:
return sync_account(self.account.pk)
finally:
close_old_connections()
from members.shkeeper import add_month
with patch("members.shkeeper.request", return_value=data), patch("members.billing.reconcile_groups", return_value=self.account.ldap_dn):
with ThreadPoolExecutor(max_workers=2) as pool:
results = list(pool.map(lambda _: verify(), range(2)))
self.assertTrue(all(results))
invoice.refresh_from_db()
self.assertIsNotNone(invoice.credited_at)
assert invoice.access_start is not None
self.assertEqual(invoice.access_end, add_month(invoice.access_start))
self.assertEqual(CryptoInvoice.objects.count(), 1)