Replace persisted event statuses with publication and cancellation flags, and compute public lifecycle labels from event timing and registration capacity. Keep filters, registration checks, and profile responses aligned with the derived status.
473 lines
24 KiB
Python
473 lines
24 KiB
Python
import pytest
|
|
import json
|
|
import redis
|
|
from datetime import timedelta
|
|
from kombu.exceptions import OperationalError
|
|
from django.utils import timezone
|
|
from core.authentication import create_jwt_token
|
|
from apps.events.models import Event, Registration
|
|
from apps.payments.models import Payment, DiscountCode
|
|
from apps.users.models import User
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.integration
|
|
class TestPaymentsAPIIntegrationTests:
|
|
password = 'PaymentPass!123'
|
|
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.user = User.objects.create_user(username='pay_user', email='pay.user@example.com', mobile='09123456789', password=cls.password)
|
|
cls.user.is_email_verified = True
|
|
cls.user.save(update_fields=['is_email_verified'])
|
|
|
|
def setup_method(self):
|
|
type(self).setUpTestData()
|
|
now = timezone.now()
|
|
self.event = Event.objects.create(title='Pay Event', description='Payment event', start_time=now + timedelta(days=2), end_time=now + timedelta(days=2, hours=2), registration_start_date=now - timedelta(days=1), registration_end_date=now + timedelta(days=1), slug='pay-event', price=50000, capacity=10, is_published=True)
|
|
self.token = create_jwt_token(self.user)
|
|
|
|
def _headers(self):
|
|
return {'HTTP_AUTHORIZATION': f'Bearer {self.token}'}
|
|
|
|
def _create_paid_event(self):
|
|
now = timezone.now()
|
|
return Event.objects.create(title='Paid Event', description='Paid', start_time=now + timedelta(days=2), end_time=now + timedelta(days=2, hours=1), registration_start_date=now - timedelta(days=1), registration_end_date=now + timedelta(days=1), slug=f'paid-{now.timestamp()}', price=20000, capacity=5, is_published=True)
|
|
|
|
def _create_discount_code(self, event):
|
|
code = DiscountCode.objects.create(code='DISC50', value=50, is_active=True)
|
|
code.applicable_events.add(event)
|
|
return code
|
|
|
|
def test_create_payment_for_free_event(self):
|
|
now = timezone.now()
|
|
free = Event.objects.create(title='Free', description='Zero', start_time=now + timedelta(days=2), end_time=now + timedelta(days=2, hours=1), registration_start_date=now - timedelta(days=1), registration_end_date=now + timedelta(days=1), slug='free-event', price=0, capacity=10, is_published=True)
|
|
response = self.client.post('/api/payments/create', data=json.dumps({'event_id': free.id, 'description': 'Free registration'}), content_type='application/json', **self._headers())
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data['amount'] == 0
|
|
assert data['start_pay_url'] is None
|
|
|
|
def test_create_payment_with_discount(self, mocker):
|
|
mock_post = mocker.patch('apps.payments.api.views.requests.post')
|
|
mock_response = mocker.Mock()
|
|
mock_response.json.return_value = {'data': {'code': 100, 'authority': 'AUTH'}}
|
|
mock_post.return_value = mock_response
|
|
code = self._create_discount_code(self.event)
|
|
response = self.client.post('/api/payments/create', data=json.dumps({'event_id': self.event.id, 'description': 'Pay with discount', 'discount_code': code.code}), content_type='application/json', **self._headers())
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert payload['discount_amount'] == self.event.price // 2
|
|
assert payload['amount'] == self.event.price // 2
|
|
assert 'start_pay_url' in payload
|
|
payment = Payment.objects.get(user=self.user, event=self.event)
|
|
assert payment.discount_code == code
|
|
|
|
def test_create_payment_reuses_an_active_attempt(self, mocker):
|
|
gateway = mocker.patch('apps.payments.api.views.requests.post')
|
|
gateway.return_value.json.return_value = {'data': {'code': 100, 'authority': 'AUTH-REUSED'}}
|
|
first = self.client.post(
|
|
'/api/payments/create',
|
|
data=json.dumps({
|
|
'event_id': self.event.id,
|
|
'description': 'First tap',
|
|
'idempotency_key': 'b0b849d1-8d8f-4f0d-94b6-7b60876f1501',
|
|
}),
|
|
content_type='application/json',
|
|
**self._headers(),
|
|
)
|
|
second = self.client.post(
|
|
'/api/payments/create',
|
|
data=json.dumps({
|
|
'event_id': self.event.id,
|
|
'description': 'Repeated tap',
|
|
'idempotency_key': 'b5f06335-1301-48c2-b504-f009c8e087d3',
|
|
}),
|
|
content_type='application/json',
|
|
**self._headers(),
|
|
)
|
|
|
|
assert first.status_code == second.status_code == 200
|
|
assert first.json()['payment_id'] == second.json()['payment_id']
|
|
assert first.json()['start_pay_url'] == second.json()['start_pay_url']
|
|
assert Payment.objects.filter(user=self.user, event=self.event).count() == 1
|
|
gateway.assert_called_once()
|
|
|
|
def test_create_payment_replays_same_key_after_gateway_timeout(self, mocker):
|
|
gateway = mocker.patch('apps.payments.api.views.requests.post', side_effect=RuntimeError('timeout'))
|
|
payload = {
|
|
'event_id': self.event.id,
|
|
'description': 'Checkout retry',
|
|
'idempotency_key': 'ed7e1426-320d-4faf-b808-7ad63e73477c',
|
|
}
|
|
|
|
first = self.client.post('/api/payments/create', data=json.dumps(payload), content_type='application/json', **self._headers())
|
|
second = self.client.post('/api/payments/create', data=json.dumps(payload), content_type='application/json', **self._headers())
|
|
|
|
assert first.status_code == second.status_code == 200
|
|
assert first.json()['status'] == second.json()['status'] == 'pending'
|
|
assert first.json()['payment_id'] == second.json()['payment_id']
|
|
assert Payment.objects.filter(user=self.user, event=self.event).count() == 1
|
|
gateway.assert_called_once()
|
|
|
|
def test_gateway_server_error_is_not_treated_as_a_definitive_failure(self, mocker):
|
|
gateway_response = mocker.Mock(status_code=503)
|
|
gateway_response.json.return_value = {"errors": {"code": -9}}
|
|
mocker.patch("apps.payments.api.views.requests.post", return_value=gateway_response)
|
|
|
|
response = self.client.post(
|
|
"/api/payments/create",
|
|
data=json.dumps({"event_id": self.event.id, "description": "Gateway server error"}),
|
|
content_type="application/json",
|
|
**self._headers(),
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["status"] == "pending"
|
|
assert Payment.objects.filter(
|
|
user=self.user,
|
|
event=self.event,
|
|
status=Payment.OrderStatusChoices.PENDING,
|
|
authority__isnull=True,
|
|
).exists()
|
|
|
|
def test_definitive_gateway_rejection_allows_a_new_attempt(self, mocker):
|
|
gateway = mocker.patch('apps.payments.api.views.requests.post')
|
|
gateway.side_effect = [
|
|
mocker.Mock(**{'json.return_value': {'errors': {'code': -9}}}),
|
|
mocker.Mock(**{'json.return_value': {'data': {'code': 100, 'authority': 'AUTH-RETRY'}}}),
|
|
]
|
|
rejected = self.client.post(
|
|
'/api/payments/create',
|
|
data=json.dumps({
|
|
'event_id': self.event.id,
|
|
'description': 'Rejected attempt',
|
|
'idempotency_key': '060826cf-b8c0-4828-b0f2-d37c0f2678dd',
|
|
}),
|
|
content_type='application/json',
|
|
**self._headers(),
|
|
)
|
|
retried = self.client.post(
|
|
'/api/payments/create',
|
|
data=json.dumps({
|
|
'event_id': self.event.id,
|
|
'description': 'New attempt',
|
|
'idempotency_key': '865f3c43-2888-4fd0-8706-1c2ae60cb5f6',
|
|
}),
|
|
content_type='application/json',
|
|
**self._headers(),
|
|
)
|
|
|
|
assert rejected.status_code == retried.status_code == 200
|
|
assert rejected.json()['status'] == 'failed'
|
|
assert retried.json()['status'] == 'pending'
|
|
assert Payment.objects.filter(user=self.user, event=self.event).count() == 2
|
|
assert Payment.objects.filter(user=self.user, event=self.event, status=Payment.OrderStatusChoices.FAILED).count() == 1
|
|
assert gateway.call_count == 2
|
|
|
|
def test_callback_success_marks_paid(self, mocker):
|
|
mock_post = mocker.patch('apps.payments.api.views.requests.post')
|
|
payment = Payment.objects.create(user=self.user, event=self.event, base_amount=self.event.price, amount=self.event.price, status=Payment.OrderStatusChoices.PENDING, authority='AUTH123')
|
|
mock_resp = mocker.Mock()
|
|
mock_resp.json.return_value = {'data': {'code': 100, 'ref_id': 'REF', 'card_pan': '123', 'card_hash': 'ABC'}}
|
|
mock_post.return_value = mock_resp
|
|
response = self.client.get('/api/payments/callback', {'Authority': 'AUTH123', 'Status': 'OK'})
|
|
payment.refresh_from_db()
|
|
assert payment.status == Payment.OrderStatusChoices.PAID
|
|
assert f'/events/{self.event.slug}/registration?payment_id={payment.id}' in response.url
|
|
assert 'status=' not in response.url
|
|
|
|
def test_callback_redirects_if_payment_notification_redis_is_unavailable(self, mocker):
|
|
mock_post = mocker.patch('apps.payments.api.views.requests.post')
|
|
self.user.is_mobile_verified = True
|
|
self.user.save(update_fields=['is_mobile_verified'])
|
|
payment = Payment.objects.create(
|
|
user=self.user,
|
|
event=self.event,
|
|
base_amount=self.event.price,
|
|
amount=self.event.price,
|
|
status=Payment.OrderStatusChoices.PENDING,
|
|
authority='AUTH-REDIS-DOWN',
|
|
)
|
|
mock_post.return_value.json.return_value = {
|
|
'data': {'code': 100, 'ref_id': 'REF-REDIS-DOWN'}
|
|
}
|
|
mocker.patch(
|
|
'apps.payments.api.views.notify_user',
|
|
side_effect=redis.exceptions.ConnectionError('Redis unavailable'),
|
|
)
|
|
mocker.patch(
|
|
'apps.payments.api.views.send_critical_sms.delay',
|
|
side_effect=OperationalError('Celery broker unavailable'),
|
|
)
|
|
|
|
response = self.client.get(
|
|
'/api/payments/callback',
|
|
{'Authority': payment.authority, 'Status': 'OK'},
|
|
)
|
|
|
|
payment.refresh_from_db()
|
|
assert response.status_code == 302
|
|
assert f'/events/{self.event.slug}/registration?payment_id={payment.id}' in response.url
|
|
assert payment.status == Payment.OrderStatusChoices.PAID
|
|
|
|
def test_callback_failure_redirects_failed(self, mocker):
|
|
mock_post = mocker.patch('apps.payments.api.views.requests.post')
|
|
payment = Payment.objects.create(user=self.user, event=self.event, base_amount=self.event.price, amount=self.event.price, status=Payment.OrderStatusChoices.PENDING, authority='AUTH456')
|
|
mock_resp = mocker.Mock()
|
|
mock_resp.json.return_value = {'data': {'code': 101, 'ref_id': 'REF'}}
|
|
mock_post.return_value = mock_resp
|
|
response = self.client.get('/api/payments/callback', {'Authority': 'AUTH456', 'Status': 'OK'})
|
|
payment.refresh_from_db()
|
|
assert payment.status == Payment.OrderStatusChoices.PAID
|
|
assert f'/events/{self.event.slug}/registration?payment_id={payment.id}' in response.url
|
|
|
|
def test_callback_missing_authority_redirects_to_safe_unknown_result(self):
|
|
response = self.client.get('/api/payments/callback', {'Status': 'OK'})
|
|
assert response.status_code == 302
|
|
assert '/events?payment_callback=unmatched' in response.url
|
|
|
|
def test_callback_unknown_authority_redirects_to_safe_unknown_result(self):
|
|
response = self.client.get(
|
|
'/api/payments/callback',
|
|
{'Authority': 'AUTH-UNKNOWN', 'Status': 'OK'},
|
|
)
|
|
assert response.status_code == 302
|
|
assert '/events?payment_callback=unmatched' in response.url
|
|
assert 'AUTH-UNKNOWN' not in response.url
|
|
|
|
def test_callback_not_ok_is_verified_before_being_canceled(self, mocker):
|
|
payment = Payment.objects.create(user=self.user, event=self.event, base_amount=self.event.price, amount=self.event.price, status=Payment.OrderStatusChoices.PENDING, authority='AUTH789')
|
|
gateway = mocker.patch('apps.payments.api.views.requests.post')
|
|
gateway.return_value.json.return_value = {'data': {'code': -51}}
|
|
response = self.client.get('/api/payments/callback', {'Authority': 'AUTH789', 'Status': 'NOK'})
|
|
payment.refresh_from_db()
|
|
assert payment.status == Payment.OrderStatusChoices.CANCELED
|
|
assert f'/events/{self.event.slug}/registration?payment_id={payment.id}' in response.url
|
|
|
|
def test_late_canceled_callback_does_not_overwrite_verified_payment(self, mocker):
|
|
registration = Registration.objects.create(
|
|
event=self.event,
|
|
user=self.user,
|
|
status=Registration.StatusChoices.PENDING,
|
|
final_price=self.event.price,
|
|
)
|
|
payment = Payment.objects.create(
|
|
user=self.user,
|
|
event=self.event,
|
|
registration=registration,
|
|
base_amount=self.event.price,
|
|
amount=self.event.price,
|
|
status=Payment.OrderStatusChoices.PENDING,
|
|
authority='AUTH-LATE-NOK',
|
|
)
|
|
mock_post = mocker.patch('apps.payments.api.views.requests.post')
|
|
mock_response = mocker.Mock()
|
|
mock_response.json.return_value = {'data': {'code': 100, 'ref_id': 'REF-LATE-NOK'}}
|
|
mock_post.return_value = mock_response
|
|
|
|
success_response = self.client.get(
|
|
'/api/payments/callback',
|
|
{'Authority': payment.authority, 'Status': 'OK'},
|
|
)
|
|
late_failure_response = self.client.get(
|
|
'/api/payments/callback',
|
|
{'Authority': payment.authority, 'Status': 'NOK'},
|
|
)
|
|
|
|
payment.refresh_from_db()
|
|
registration.refresh_from_db()
|
|
assert payment.status == Payment.OrderStatusChoices.PAID
|
|
assert payment.verified_at is not None
|
|
assert registration.status == Registration.StatusChoices.CONFIRMED
|
|
expected_return = f'/events/{self.event.slug}/registration?registration_id={registration.ticket_id}'
|
|
assert expected_return in success_response.url
|
|
assert expected_return in late_failure_response.url
|
|
|
|
def test_create_payment_gateway_timeout_stays_pending(self, mocker):
|
|
mocker.patch('apps.payments.api.views.requests.post', side_effect=RuntimeError('down'))
|
|
response = self.client.post('/api/payments/create', data=json.dumps({'event_id': self.event.id, 'description': 'Gateway fail'}), content_type='application/json', **self._headers())
|
|
assert response.status_code == 200
|
|
assert response.json()['status'] == 'pending'
|
|
assert Payment.objects.filter(user=self.user, event=self.event, status=Payment.OrderStatusChoices.PENDING).exists()
|
|
|
|
def test_verification_timeout_stays_pending_and_queues_reconciliation(self, mocker):
|
|
payment = Payment.objects.create(user=self.user, event=self.event, base_amount=self.event.price, amount=self.event.price, status=Payment.OrderStatusChoices.PENDING, authority='AUTH-TIMEOUT')
|
|
mocker.patch('apps.payments.api.views.requests.post', side_effect=RuntimeError('gateway timeout'))
|
|
reconcile = mocker.patch('apps.payments.api.views.reconcile_payment.apply_async')
|
|
notify = mocker.patch('apps.payments.api.views.notify_user')
|
|
|
|
response = self.client.get('/api/payments/callback', {'Authority': payment.authority, 'Status': 'OK'})
|
|
|
|
payment.refresh_from_db()
|
|
assert response.status_code == 302
|
|
assert f'/events/{self.event.slug}/registration?payment_id={payment.id}' in response.url
|
|
assert payment.status == Payment.OrderStatusChoices.PENDING
|
|
reconcile.assert_called_once_with(args=(payment.id,), countdown=60)
|
|
notify.assert_not_called()
|
|
|
|
def test_payment_status_is_user_scoped_and_reflects_saved_state(self):
|
|
payment = Payment.objects.create(user=self.user, event=self.event, base_amount=self.event.price, amount=self.event.price, status=Payment.OrderStatusChoices.PENDING, authority='AUTH-STATUS')
|
|
own = self.client.get(f'/api/payments/status/{payment.id}', **self._headers())
|
|
assert own.status_code == 200
|
|
assert own.json()['payment_id'] == payment.id
|
|
assert own.json()['status'] == 'pending'
|
|
assert own.json()['verification_state'] == 'awaiting_verification'
|
|
assert own.json()['verification_attempts'] == 0
|
|
assert own.json()['last_verification_at'] is None
|
|
assert own.json()['manual_review_at'] is None
|
|
|
|
other = User.objects.create_user(username='other_payment_owner', email='other-payment@example.com', mobile='09123456780', password=self.password)
|
|
other_token = create_jwt_token(other)
|
|
forbidden = self.client.get(f'/api/payments/status/{payment.id}', HTTP_AUTHORIZATION=f'Bearer {other_token}')
|
|
assert forbidden.status_code == 404
|
|
|
|
def test_payment_reference_lookup_requires_authentication_and_is_user_scoped(self):
|
|
payment = Payment.objects.create(
|
|
user=self.user,
|
|
event=self.event,
|
|
base_amount=self.event.price,
|
|
amount=self.event.price,
|
|
status=Payment.OrderStatusChoices.PAID,
|
|
ref_id="OWNER-ONLY-REF",
|
|
)
|
|
|
|
unauthenticated = self.client.get("/api/payments/by-ref/OWNER-ONLY-REF")
|
|
owned = self.client.get("/api/payments/by-ref/OWNER-ONLY-REF", **self._headers())
|
|
other = User.objects.create_user(
|
|
username="other_ref_owner",
|
|
email="other-ref-owner@example.com",
|
|
mobile="09123456781",
|
|
password=self.password,
|
|
)
|
|
other_token = create_jwt_token(other)
|
|
forbidden = self.client.get(
|
|
"/api/payments/by-ref/OWNER-ONLY-REF",
|
|
HTTP_AUTHORIZATION=f"Bearer {other_token}",
|
|
)
|
|
|
|
assert unauthenticated.status_code == 401
|
|
assert owned.status_code == 200
|
|
assert owned.json()["payment_id"] == payment.id
|
|
assert forbidden.status_code == 404
|
|
|
|
def test_create_payment_when_already_paid(self):
|
|
Payment.objects.create(user=self.user, event=self.event, base_amount=self.event.price, amount=self.event.price, status=Payment.OrderStatusChoices.PAID)
|
|
response = self.client.post('/api/payments/create', data=json.dumps({'event_id': self.event.id, 'description': 'Duplicate'}), content_type='application/json', **self._headers())
|
|
assert response.status_code == 400
|
|
|
|
def test_registration_final_price_none_updates(self, mocker):
|
|
mock_post = mocker.patch('apps.payments.api.views.requests.post')
|
|
registration = Registration.objects.create(event=self.event, user=self.user, status=Registration.StatusChoices.PENDING, final_price=None)
|
|
mock_response = mocker.Mock()
|
|
mock_response.json.return_value = {'data': {'code': 100, 'authority': 'AUTH'}}
|
|
mock_post.return_value = mock_response
|
|
response = self.client.post('/api/payments/create', data=json.dumps({'event_id': self.event.id, 'description': 'Update'}), content_type='application/json', **self._headers())
|
|
assert response.status_code == 200
|
|
registration.refresh_from_db()
|
|
if registration.final_price is None:
|
|
pytest.fail('final_price should be populated')
|
|
|
|
def test_coupon_check_success_and_errors(self):
|
|
code = DiscountCode.objects.create(code='PAYCO', value=20, is_active=True, type=DiscountCode.Type.PERCENT)
|
|
code.applicable_events.add(self.event)
|
|
missing = self.client.post('/api/payments/coupon/check', data=json.dumps({'event_id': self.event.id}), content_type='application/json', **self._headers())
|
|
assert missing.status_code == 422
|
|
invalid = self.client.post('/api/payments/coupon/check', data=json.dumps({'event_id': self.event.id, 'code': 'INVALID'}), content_type='application/json', **self._headers())
|
|
assert invalid.status_code == 404
|
|
success = self.client.post('/api/payments/coupon/check', data=json.dumps({'event_id': self.event.id, 'code': code.code}), content_type='application/json', **self._headers())
|
|
assert success.status_code == 200
|
|
assert 'final_price' in success.json()
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _pytest_client(self, client):
|
|
self.client = client
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _pytest_settings(self, settings):
|
|
settings.ZARINPAL_MERCHANT_ID = 'MID'
|
|
settings.ZARINPAL_REQUEST_URL = 'https://zarinpal/request'
|
|
settings.ZARINPAL_STARTPAY = 'https://zarinpal/start/'
|
|
settings.ZARINPAL_VERIFY_URL = 'https://zarinpal/verify'
|
|
settings.ZARINPAL_CALLBACK_URL = 'https://frontend/callback'
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.integration
|
|
def test_my_payments_are_user_scoped_and_omit_gateway_identifiers(client, user_factory, auth_headers, event_factory):
|
|
current_user = user_factory(username="payment_history_owner")
|
|
other_user = user_factory(username="payment_history_other")
|
|
own_event = event_factory(slug="payment-history-own", title="My workshop")
|
|
other_event = event_factory(slug="payment-history-other", title="Someone else's workshop")
|
|
Payment.objects.create(
|
|
user=current_user,
|
|
event=own_event,
|
|
base_amount=120000,
|
|
discount_amount=20000,
|
|
amount=100000,
|
|
status=Payment.OrderStatusChoices.PAID,
|
|
authority="internal-authority-owner",
|
|
ref_id="internal-reference-owner",
|
|
card_pan="6037-****-****-1234",
|
|
card_hash="internal-card-hash",
|
|
)
|
|
Payment.objects.create(
|
|
user=other_user,
|
|
event=other_event,
|
|
base_amount=50000,
|
|
amount=50000,
|
|
status=Payment.OrderStatusChoices.PENDING,
|
|
authority="internal-authority-other",
|
|
)
|
|
|
|
response = client.get("/api/payments/me", **auth_headers(current_user))
|
|
assert response.status_code == 200, response.content
|
|
payload = response.json()
|
|
assert len(payload) == 1
|
|
assert payload[0]["event"]["title"] == own_event.title
|
|
assert payload[0]["event"]["id"] == own_event.id
|
|
assert payload[0]["amount"] == 100000
|
|
assert payload[0]["discount_amount"] == 20000
|
|
assert payload[0]["status"] == "paid"
|
|
assert set(payload[0]) == {"payment_id", "event", "base_amount", "discount_amount", "amount", "status", "refund_status", "created_at", "paid_at"}
|
|
assert payload[0]["payment_id"] == Payment.objects.get(user=current_user, event=own_event).id
|
|
assert "internal-authority-owner" not in response.content.decode()
|
|
assert "internal-reference-owner" not in response.content.decode()
|
|
assert "internal-card-hash" not in response.content.decode()
|
|
|
|
assert client.get("/api/payments/me").status_code == 401
|
|
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.integration
|
|
def test_my_payments_pagination_is_user_scoped(client, user_factory, auth_headers, event_factory):
|
|
current_user = user_factory(username="payment_paging_owner")
|
|
other_user = user_factory(username="payment_paging_other")
|
|
event = event_factory(slug="payment-paging-event")
|
|
for index in range(12):
|
|
Payment.objects.create(
|
|
user=current_user,
|
|
event=event,
|
|
base_amount=1000 + index,
|
|
amount=1000 + index,
|
|
status=Payment.OrderStatusChoices.PAID,
|
|
)
|
|
Payment.objects.create(
|
|
user=other_user,
|
|
event=event,
|
|
base_amount=9000,
|
|
amount=9000,
|
|
status=Payment.OrderStatusChoices.PAID,
|
|
)
|
|
|
|
first_page = client.get("/api/payments/me/paginated", {"page": 1}, **auth_headers(current_user))
|
|
second_page = client.get("/api/payments/me/paginated", {"page": 2}, **auth_headers(current_user))
|
|
|
|
assert first_page.status_code == 200, first_page.content
|
|
assert first_page.json()["count"] == 12
|
|
assert first_page.json()["page_size"] == 10
|
|
assert first_page.json()["total_pages"] == 2
|
|
assert len(first_page.json()["results"]) == 10
|
|
assert len(second_page.json()["results"]) == 2
|
|
assert all(item["event"]["slug"] == event.slug for item in first_page.json()["results"])
|
|
assert "authority" not in first_page.json()["results"][0]
|