Split backend tests into unit and integration suites, add isolated fixtures and coverage enforcement, and update CI and test documentation. Cover current authentication, content, notification, payment, media, and admin flows; fix behaviors revealed by the new tests. Validate both suites and the per-module gate against local PostgreSQL.
149 lines
6.5 KiB
Python
149 lines
6.5 KiB
Python
import pytest
|
|
import uuid
|
|
from datetime import timedelta
|
|
from types import SimpleNamespace
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils import timezone
|
|
from django.contrib.admin import AdminSite
|
|
from apps.payments.admin import DiscountCodeAdmin
|
|
from apps.payments.models import DiscountCode, Payment
|
|
from apps.payments.resources import DiscountResource, PaymentResource
|
|
from apps.events.models import Event
|
|
from apps.users.models import User
|
|
from ninja.errors import HttpError
|
|
|
|
class PaymentTestMixin:
|
|
|
|
@staticmethod
|
|
def _create_user(**overrides):
|
|
data = {'username': f'user_{uuid.uuid4().hex[:6]}', 'email': f'user_{uuid.uuid4().hex[:6]}@example.com', 'mobile': f'09{uuid.uuid4().int % 1000000000:09d}', 'password': 'Test!1234'}
|
|
data.update(overrides)
|
|
return User.objects.create_user(**data)
|
|
|
|
@staticmethod
|
|
def _create_event(**overrides):
|
|
now = timezone.now()
|
|
defaults = {'title': 'Sample', 'description': 'Desc', 'start_time': now, 'end_time': now + timedelta(hours=2), 'registration_start_date': now - timedelta(days=1), 'registration_end_date': now + timedelta(days=5), 'slug': f'event-{uuid.uuid4().hex[:6]}', 'price': 100000, 'capacity': 10, 'status': Event.StatusChoices.PUBLISHED}
|
|
defaults.update(overrides)
|
|
return Event.objects.create(**defaults)
|
|
|
|
@staticmethod
|
|
def _discount_code(**overrides):
|
|
defaults = {'code': f'CODE{uuid.uuid4().hex[:4]}', 'value': 50, 'is_active': True, 'type': DiscountCode.Type.PERCENT}
|
|
defaults.update(overrides)
|
|
return DiscountCode.objects.create(**defaults)
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.integration
|
|
class TestDiscountCodeModelTests(PaymentTestMixin):
|
|
|
|
def setup_method(self):
|
|
self.event = self._create_event()
|
|
self.user = self._create_user(is_email_verified=True)
|
|
|
|
def test_zero_price_returns_zero_discount(self):
|
|
event = self._create_event(price=0)
|
|
code = self._discount_code()
|
|
code.applicable_events.add(event)
|
|
assert code.calculate_discount(event, self.user) == (0, 0)
|
|
|
|
def test_inactive_raises_error(self):
|
|
code = self._discount_code(is_active=False)
|
|
code.applicable_events.add(self.event)
|
|
with pytest.raises(HttpError):
|
|
code.calculate_discount(self.event, self.user)
|
|
|
|
def test_start_date_validation(self):
|
|
code = self._discount_code(starts_at=timezone.now() + timedelta(days=1))
|
|
code.applicable_events.add(self.event)
|
|
with pytest.raises(HttpError):
|
|
code.calculate_discount(self.event, self.user)
|
|
|
|
def test_end_date_validation(self):
|
|
code = self._discount_code(ends_at=timezone.now() - timedelta(days=1))
|
|
code.applicable_events.add(self.event)
|
|
with pytest.raises(HttpError):
|
|
code.calculate_discount(self.event, self.user)
|
|
|
|
def test_applicable_events_enforcement(self):
|
|
code = self._discount_code()
|
|
other_event = self._create_event()
|
|
code.applicable_events.add(other_event)
|
|
with pytest.raises(HttpError):
|
|
code.calculate_discount(self.event, self.user)
|
|
|
|
def test_min_amount_guard(self):
|
|
code = self._discount_code(min_amount=200000)
|
|
code.applicable_events.add(self.event)
|
|
with pytest.raises(HttpError):
|
|
code.calculate_discount(self.event, self.user)
|
|
|
|
def test_usage_limit_total(self):
|
|
code = self._discount_code(usage_limit_total=1)
|
|
code.applicable_events.add(self.event)
|
|
Payment.objects.create(user=self.user, event=self.event, base_amount=self.event.price, amount=self.event.price, discount_amount=0, status=Payment.OrderStatusChoices.PAID, discount_code=code)
|
|
with pytest.raises(HttpError):
|
|
code.calculate_discount(self.event, self.user)
|
|
|
|
def test_usage_limit_per_user(self):
|
|
code = self._discount_code(usage_limit_per_user=1)
|
|
code.applicable_events.add(self.event)
|
|
Payment.objects.create(user=self.user, event=self.event, base_amount=self.event.price, amount=self.event.price, discount_amount=0, status=Payment.OrderStatusChoices.PENDING, discount_code=code)
|
|
with pytest.raises(HttpError):
|
|
code.calculate_discount(self.event, self.user)
|
|
|
|
def test_final_price_below_min_post_discount(self):
|
|
event = self._create_event(price=15000)
|
|
code = self._discount_code(value=80)
|
|
code.applicable_events.add(event)
|
|
with pytest.raises(HttpError):
|
|
code.calculate_discount(event, self.user)
|
|
|
|
def test_fixed_discount_type(self):
|
|
code = self._discount_code(type=DiscountCode.Type.FIXED, value=5000)
|
|
code.applicable_events.add(self.event)
|
|
final, disc = code.calculate_discount(self.event, self.user)
|
|
assert disc == 5000
|
|
assert final == self.event.price - 5000
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.integration
|
|
class TestPaymentModelAndResourceTests(PaymentTestMixin):
|
|
|
|
def setup_method(self):
|
|
self.event = self._create_event()
|
|
self.user = self._create_user(is_email_verified=True)
|
|
|
|
def test_payment_clean_validates_amount(self):
|
|
payment = Payment(user=self.user, event=self.event, base_amount=1000, amount=500, discount_amount=400, status=Payment.OrderStatusChoices.INIT)
|
|
with pytest.raises(ValidationError):
|
|
payment.full_clean()
|
|
|
|
def test_payment_resource_defers_user_event(self):
|
|
payment = Payment.objects.create(user=self.user, event=self.event, base_amount=1000, amount=1000, discount_amount=0, status=Payment.OrderStatusChoices.INIT)
|
|
resource = PaymentResource()
|
|
user_cell = resource.fields['user'].widget.clean(self.user.username, None)
|
|
assert user_cell == self.user
|
|
event_cell = resource.fields['event'].widget.clean(self.event.title, None)
|
|
assert event_cell == self.event
|
|
|
|
def test_discount_resource_expands_events(self):
|
|
resource = DiscountResource()
|
|
widget = resource.fields['event'].widget
|
|
assert widget.separator == '||'
|
|
|
|
@pytest.mark.django_db
|
|
@pytest.mark.integration
|
|
class TestDiscountCodeAdminTests(PaymentTestMixin):
|
|
|
|
def setup_method(self):
|
|
self.admin = DiscountCodeAdmin(DiscountCode, AdminSite())
|
|
|
|
def test_deactivate_codes_action(self):
|
|
code = self._discount_code()
|
|
queryset = DiscountCode.objects.filter(pk=code.pk)
|
|
request = SimpleNamespace(_messages=SimpleNamespace(add=lambda *args, **kwargs: None))
|
|
self.admin.deactivate_codes(request, queryset)
|
|
code.refresh_from_db()
|
|
assert not code.is_active
|