96 lines
5.0 KiB
Python
96 lines
5.0 KiB
Python
import json
|
|
|
|
import pytest
|
|
|
|
from apps.events.models import Registration
|
|
from apps.payments.models import DiscountCode, Payment
|
|
|
|
|
|
pytestmark = [pytest.mark.integration, pytest.mark.django_db]
|
|
|
|
|
|
def _send(client, method, path, headers, payload=None):
|
|
kwargs = dict(headers)
|
|
if payload is not None:
|
|
kwargs.update(data=json.dumps(payload), content_type="application/json")
|
|
return getattr(client, method)(path, **kwargs)
|
|
|
|
|
|
def test_discount_code_admin_crud_and_restore(client, user_factory, auth_headers, event_factory):
|
|
staff = user_factory(is_staff=True)
|
|
admin = user_factory(is_staff=True, is_superuser=True)
|
|
member = user_factory()
|
|
event = event_factory(price=10000)
|
|
path = "/api/payments/admin/discount-codes"
|
|
payload = {"code": "pytest10", "type": "percent", "value": 10, "applicable_event_ids": [event.pk]}
|
|
assert _send(client, "get", path, auth_headers(member)).status_code == 403
|
|
assert _send(client, "post", path, auth_headers(member), payload).status_code == 403
|
|
created = _send(client, "post", path, auth_headers(staff), payload)
|
|
assert created.status_code == 201, created.content
|
|
code = DiscountCode.objects.get(pk=created.json()["id"])
|
|
assert code.code == "PYTEST10" and list(code.applicable_events.all()) == [event]
|
|
assert _send(client, "post", path, auth_headers(staff), payload).status_code == 400
|
|
assert _send(client, "get", path + "?search=PYTEST10", auth_headers(staff)).json()["count"] == 1
|
|
changed = _send(client, "put", f"{path}/{code.pk}", auth_headers(staff), {**payload, "value": 20})
|
|
assert changed.status_code == 200, changed.content
|
|
code.refresh_from_db()
|
|
assert code.value == 20
|
|
assert _send(client, "delete", f"{path}/{code.pk}", auth_headers(staff)).status_code == 403
|
|
assert _send(client, "delete", f"{path}/{code.pk}", auth_headers(admin)).status_code == 200
|
|
assert DiscountCode.deleted_objects.filter(pk=code.pk).exists()
|
|
assert _send(client, "post", f"{path}/{code.pk}/restore", auth_headers(admin)).status_code == 200
|
|
assert DiscountCode.objects.filter(pk=code.pk).exists()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"gateway_result,expected_status,expected_payment_status",
|
|
[({"errors": {"code": -9}}, "failed", Payment.OrderStatusChoices.FAILED), (None, "pending", Payment.OrderStatusChoices.PENDING)],
|
|
)
|
|
def test_payment_create_gateway_failure_is_persisted_conservatively(
|
|
client, user_factory, auth_headers, event_factory, mocker, settings,
|
|
gateway_result, expected_status, expected_payment_status,
|
|
):
|
|
settings.ZARINPAL_MERCHANT_ID = "test"
|
|
settings.ZARINPAL_REQUEST_URL = "https://gateway.example/request"
|
|
user = user_factory()
|
|
event = event_factory(price=25000)
|
|
gateway = mocker.patch("apps.payments.api.views.requests.post")
|
|
if gateway_result is None:
|
|
gateway.side_effect = RuntimeError("offline")
|
|
else:
|
|
gateway.return_value.json.return_value = gateway_result
|
|
response = _send(client, "post", "/api/payments/create", auth_headers(user),
|
|
{"event_id": event.pk, "description": "Ticket"})
|
|
assert response.status_code == 200
|
|
assert response.json()["status"] == expected_status
|
|
assert Payment.objects.filter(user=user, event=event, status=expected_payment_status).count() == 1
|
|
assert Registration.objects.filter(user=user, event=event).exists()
|
|
|
|
|
|
@pytest.mark.parametrize("gateway_result", [None, {"data": {"code": -1}}])
|
|
def test_payment_callback_verification_failures_notify_once(client, user_factory, event_factory, mocker,
|
|
settings, gateway_result):
|
|
settings.ZARINPAL_MERCHANT_ID = "test"
|
|
settings.ZARINPAL_VERIFY_URL = "https://gateway.example/verify"
|
|
settings.FRONTEND_CALLBACK_URL = "https://frontend.example/result"
|
|
user = user_factory()
|
|
event = event_factory(price=25000)
|
|
registration = Registration.objects.create(event=event, user=user, status=Registration.StatusChoices.PENDING)
|
|
payment = Payment.objects.create(user=user, event=event, registration=registration, amount=25000,
|
|
base_amount=25000, authority="AUTH-FAIL", status=Payment.OrderStatusChoices.PENDING)
|
|
gateway = mocker.patch("apps.payments.api.views.requests.post")
|
|
notify = mocker.patch("apps.payments.api.views.notify_user")
|
|
retry = mocker.patch("apps.payments.api.views.reconcile_payment.apply_async")
|
|
if gateway_result is None:
|
|
gateway.side_effect = RuntimeError("offline")
|
|
else:
|
|
gateway.return_value.json.return_value = gateway_result
|
|
response = client.get("/api/payments/callback", {"Authority": "AUTH-FAIL", "Status": "OK"})
|
|
assert response.status_code == 302 and "status=pending" in response.url
|
|
payment.refresh_from_db()
|
|
registration.refresh_from_db()
|
|
assert payment.status == Payment.OrderStatusChoices.PENDING
|
|
assert registration.status == Registration.StatusChoices.PENDING
|
|
notify.assert_not_called()
|
|
retry.assert_called_once_with(args=(payment.id,), countdown=60)
|