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", [({"errors": {"code": -9}}, 502), (None, 502)]) def test_payment_create_gateway_failures_leave_no_active_payment(client, user_factory, auth_headers, event_factory, mocker, settings, gateway_result, expected): 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 == expected assert not Payment.objects.filter(user=user, event=event).exists() 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") 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=failed" in response.url payment.refresh_from_db() registration.refresh_from_db() assert payment.status == Payment.OrderStatusChoices.FAILED assert registration.status == Registration.StatusChoices.PENDING notify.assert_called_once()