50 lines
2.2 KiB
Python
50 lines
2.2 KiB
Python
"""Run against an isolated PostgreSQL test DB; SQLite cannot prove row-lock semantics.
|
|
USE_SQLITE=false POSTGRES_* ... pytest tests/test_payment_concurrency.py
|
|
Never point POSTGRES_DB at a production database.
|
|
"""
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from threading import Barrier
|
|
import pytest
|
|
from django.db import connection, close_old_connections
|
|
from django.core.exceptions import ValidationError
|
|
from apps.accounts.models import User
|
|
from apps.notifications.models import NotificationJob
|
|
from apps.payments import services
|
|
from apps.payments.models import PaymentReceipt, ReceiptDecision
|
|
from tests.test_payments import case, submit
|
|
|
|
pytestmark = [pytest.mark.django_db(transaction=True),
|
|
pytest.mark.skipif(connection.vendor != 'postgresql', reason='Requires PostgreSQL row locks')]
|
|
|
|
|
|
@pytest.mark.parametrize('actors,actions,final', [
|
|
(('first','second'), ('APPROVE','APPROVE'), 'APPROVED'),
|
|
(('first','first'), ('APPROVE','APPROVE'), 'PARTIAL'),
|
|
(('first','second'), ('APPROVE','REJECT'), 'REJECTED'),
|
|
(('first','senior'), ('APPROVE','SENIOR_APPROVE'), 'APPROVED'),
|
|
])
|
|
def test_concurrent_decisions_have_one_consistent_final_result(case, actors, actions, final):
|
|
receipt = submit(case)
|
|
barrier = Barrier(2)
|
|
def decide(index):
|
|
close_old_connections()
|
|
try:
|
|
actor = User.objects.get(pk=case[actors[index]].pk)
|
|
barrier.wait(timeout=10)
|
|
try:
|
|
services.decide_receipt(actor, receipt.public_id, 1, actions[index], 'بررسی پرداخت')
|
|
return 'recorded'
|
|
except ValidationError:
|
|
return 'already finalized'
|
|
finally:
|
|
connection.close()
|
|
with ThreadPoolExecutor(max_workers=2) as pool:
|
|
outcomes = list(pool.map(decide, range(2)))
|
|
receipt.refresh_from_db()
|
|
assert receipt.status == final
|
|
assert receipt.revisions.get().cycle.status == final
|
|
assert outcomes.count('recorded') >= 1
|
|
votes = ReceiptDecision.objects.filter(cycle__revision__receipt=receipt)
|
|
assert votes.count() == (1 if actors[0] == actors[1] else outcomes.count('recorded'))
|
|
assert NotificationJob.objects.filter(event_type='PAYMENT_RECEIPT_APPROVED').count() == (1 if final == 'APPROVED' else 0)
|