Files
customerTicket/tests/test_payment_concurrency.py
2026-10-06 10:03:22 +03:30

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)