Test pay with sith

This commit is contained in:
Antoine Bartuccio 2025-04-23 00:11:50 +02:00
parent 8af6af1303
commit 2a7c1a6438
7 changed files with 163 additions and 43 deletions

View File

@ -47,6 +47,10 @@ from counter.fields import CurrencyField
from subscription.models import Subscription from subscription.models import Subscription
def get_eboutic() -> Counter:
return Counter.objects.filter(type="EBOUTIC").order_by("id").first()
class CustomerQuerySet(models.QuerySet): class CustomerQuerySet(models.QuerySet):
def update_amount(self) -> int: def update_amount(self) -> int:
"""Update the amount of all customers selected by this queryset. """Update the amount of all customers selected by this queryset.

View File

@ -28,12 +28,20 @@ from django.utils.translation import gettext_lazy as _
from core.models import User from core.models import User
from counter.fields import CurrencyField from counter.fields import CurrencyField
from counter.models import BillingInfo, Counter, Customer, Product, Refilling, Selling from counter.models import (
BillingInfo,
Counter,
Customer,
Product,
Refilling,
Selling,
get_eboutic,
)
def get_eboutic_products(user: User) -> list[Product]: def get_eboutic_products(user: User) -> list[Product]:
products = ( products = (
Counter.objects.get(type="EBOUTIC") get_eboutic()
.products.filter(product_type__isnull=False) .products.filter(product_type__isnull=False)
.filter(archived=False) .filter(archived=False)
.filter(limit_age__lte=user.age) .filter(limit_age__lte=user.age)
@ -102,13 +110,6 @@ class Basket(models.Model):
)["total"] )["total"]
) )
@classmethod
def from_session(cls, session) -> Basket | None:
"""The basket stored in the session object, if it exists."""
if "basket_id" in session:
return cls.objects.filter(id=session["basket_id"]).first()
return None
def generate_sales(self, counter, seller: User, payment_method: str): def generate_sales(self, counter, seller: User, payment_method: str):
"""Generate a list of sold items corresponding to the items """Generate a list of sold items corresponding to the items
of this basket WITHOUT saving them NOR deleting the basket. of this basket WITHOUT saving them NOR deleting the basket.

View File

@ -4,6 +4,14 @@
<h3>{% trans %}Eboutic{% endtrans %}</h3> <h3>{% trans %}Eboutic{% endtrans %}</h3>
<div> <div>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% if success %} {% if success %}
{% trans %}Payment successful{% endtrans %} {% trans %}Payment successful{% endtrans %}
{% else %} {% else %}

View File

@ -1,3 +1,4 @@
import pytest
from django.http import HttpResponse from django.http import HttpResponse
from django.test import TestCase from django.test import TestCase
from django.test.client import Client from django.test.client import Client
@ -9,11 +10,20 @@ from pytest_django.asserts import assertRedirects
from core.baker_recipes import subscriber_user from core.baker_recipes import subscriber_user
from core.models import Group, User from core.models import Group, User
from counter.baker_recipes import product_recipe from counter.baker_recipes import product_recipe
from counter.models import Counter, ProductType from counter.models import Counter, ProductType, get_eboutic
from counter.tests.test_counter import BasketItem from counter.tests.test_counter import BasketItem
from eboutic.models import Basket from eboutic.models import Basket
@pytest.mark.django_db
def test_get_eboutic():
assert Counter.objects.get(name="Eboutic") == get_eboutic()
baker.make(Counter, type="EBOUTIC")
assert Counter.objects.get(name="Eboutic") == get_eboutic()
class TestEboutic(TestCase): class TestEboutic(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
@ -51,7 +61,7 @@ class TestEboutic(TestCase):
cls.new_customer.groups.add(cls.group_public) cls.new_customer.groups.add(cls.group_public)
cls.new_customer_adult.groups.add(cls.group_public) cls.new_customer_adult.groups.add(cls.group_public)
cls.eboutic = Counter.objects.get(name="Eboutic") cls.eboutic = get_eboutic()
cls.eboutic.products.add(cls.cotiz, cls.beer, cls.snack) cls.eboutic.products.add(cls.cotiz, cls.beer, cls.snack)
@classmethod @classmethod

View File

@ -0,0 +1,122 @@
from decimal import Decimal
from django.contrib.messages import get_messages
from django.contrib.messages.constants import DEFAULT_LEVELS
from django.test import TestCase
from django.urls import reverse
from model_bakery import baker
from pytest_django.asserts import assertRedirects
from core.baker_recipes import subscriber_user
from counter.baker_recipes import product_recipe
from counter.models import Product, ProductType
from counter.tests.test_counter import force_refill_user
from eboutic.models import Basket, BasketItem
class TestPaymentBase(TestCase):
@classmethod
def setUpTestData(cls):
cls.customer = subscriber_user.make()
cls.basket = baker.make(Basket, user=cls.customer)
cls.refilling = Product.objects.get(code="15REFILL")
product_type = baker.make(ProductType)
cls.snack = product_recipe.make(
selling_price=1.5, special_selling_price=1, product_type=product_type
)
cls.beer = product_recipe.make(
limit_age=18,
selling_price=2.5,
special_selling_price=1,
product_type=product_type,
)
BasketItem.from_product(cls.snack, 1, cls.basket).save()
BasketItem.from_product(cls.beer, 2, cls.basket).save()
class TestPaymentSith(TestPaymentBase):
def test_anonymous(self):
assert (
self.client.post(
reverse("eboutic:pay_with_sith", kwargs={"basket_id": self.basket.id}),
).status_code
== 403
)
assert Basket.objects.filter(id=self.basket.id).first() is not None
def test_unauthorized(self):
self.client.force_login(subscriber_user.make())
assert (
self.client.post(
reverse("eboutic:pay_with_sith", kwargs={"basket_id": self.basket.id}),
).status_code
== 403
)
assert Basket.objects.filter(id=self.basket.id).first() is not None
def test_not_found(self):
self.client.force_login(self.customer)
assert (
self.client.post(
reverse(
"eboutic:pay_with_sith", kwargs={"basket_id": self.basket.id + 1}
),
).status_code
== 404
)
assert Basket.objects.filter(id=self.basket.id).first() is not None
def test_buy_success(self):
self.client.force_login(self.customer)
force_refill_user(self.customer, self.basket.total + 1)
assertRedirects(
self.client.post(
reverse("eboutic:pay_with_sith", kwargs={"basket_id": self.basket.id}),
),
reverse("eboutic:payment_result", kwargs={"result": "success"}),
)
assert Basket.objects.filter(id=self.basket.id).first() is None
self.customer.customer.refresh_from_db()
assert self.customer.customer.amount == Decimal("1")
def test_not_enough_money(self):
self.client.force_login(self.customer)
response = self.client.post(
reverse("eboutic:pay_with_sith", kwargs={"basket_id": self.basket.id}),
)
assertRedirects(
response,
reverse("eboutic:payment_result", kwargs={"result": "failure"}),
)
messages = list(get_messages(response.wsgi_request))
assert len(messages) == 1
assert messages[0].level == DEFAULT_LEVELS["ERROR"]
assert messages[0].message == "Solde insuffisant"
assert Basket.objects.filter(id=self.basket.id).first() is not None
def test_refilling_in_basket(self):
BasketItem.from_product(self.refilling, 1, self.basket).save()
self.client.force_login(self.customer)
force_refill_user(self.customer, self.basket.total)
response = self.client.post(
reverse("eboutic:pay_with_sith", kwargs={"basket_id": self.basket.id}),
)
assertRedirects(
response,
reverse("eboutic:payment_result", kwargs={"result": "failure"}),
)
assert Basket.objects.filter(id=self.basket.id).first() is not None
messages = list(get_messages(response.wsgi_request))
assert messages[0].level == DEFAULT_LEVELS["ERROR"]
assert (
messages[0].message
== "Vous ne pouvez pas acheter un rechargement avec de l'argent du sith"
)
self.customer.customer.refresh_from_db()
assert self.customer.customer.amount == self.basket.total

View File

@ -85,33 +85,6 @@ class TestEboutic(TestCase):
) )
return url return url
def test_buy_with_sith_account(self):
self.client.force_login(self.subscriber)
self.subscriber.customer.amount = 100 # give money before test
self.subscriber.customer.save()
basket = self.get_busy_basket(self.subscriber)
amount = basket.total
response = self.client.post(reverse("eboutic:pay_with_sith"))
self.assertRedirects(response, "/eboutic/pay/success/")
new_balance = Customer.objects.get(user=self.subscriber).amount
assert float(new_balance) == 100 - amount
expected = 'basket_items=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/eboutic'
assert expected == self.client.cookies["basket_items"].OutputString()
def test_buy_with_sith_account_no_money(self):
self.client.force_login(self.subscriber)
basket = self.get_busy_basket(self.subscriber)
initial = basket.total - 1 # just not enough to complete the sale
self.subscriber.customer.amount = initial
self.subscriber.customer.save()
response = self.client.post(reverse("eboutic:pay_with_sith"))
self.assertRedirects(response, "/eboutic/pay/failure/")
new_balance = Customer.objects.get(user=self.subscriber).amount
assert float(new_balance) == initial
# this cookie should be removed after payment
expected = 'basket_items=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/eboutic'
assert expected == self.client.cookies["basket_items"].OutputString()
def test_buy_subscribe_product_with_credit_card(self): def test_buy_subscribe_product_with_credit_card(self):
self.client.force_login(self.old_subscriber) self.client.force_login(self.old_subscriber)
response = self.client.get( response = self.client.get(

View File

@ -32,7 +32,7 @@ from django.contrib.auth.mixins import (
LoginRequiredMixin, LoginRequiredMixin,
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation, ValidationError
from django.db import DatabaseError, transaction from django.db import DatabaseError, transaction
from django.db.models.fields import forms from django.db.models.fields import forms
from django.db.utils import cached_property from django.db.utils import cached_property
@ -48,7 +48,7 @@ from django_countries.fields import Country
from core.auth.mixins import CanViewMixin, IsSubscriberMixin from core.auth.mixins import CanViewMixin, IsSubscriberMixin
from core.views.mixins import FragmentMixin, UseFragmentsMixin from core.views.mixins import FragmentMixin, UseFragmentsMixin
from counter.forms import BaseBasketForm, BillingInfoForm, ProductForm from counter.forms import BaseBasketForm, BillingInfoForm, ProductForm
from counter.models import BillingInfo, Counter, Customer, Product, Selling from counter.models import BillingInfo, Customer, Product, Selling, get_eboutic
from eboutic.models import ( from eboutic.models import (
Basket, Basket,
BasketItem, BasketItem,
@ -90,7 +90,7 @@ class EbouticMainView(LoginRequiredMixin, FormView):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs["form_kwargs"] = { kwargs["form_kwargs"] = {
"customer": self.customer, "customer": self.customer,
"counter": Counter.objects.get(type="EBOUTIC"), "counter": get_eboutic(),
"allowed_products": {product.id: product for product in self.products}, "allowed_products": {product.id: product for product in self.products},
} }
return kwargs return kwargs
@ -246,9 +246,9 @@ class EbouticPayWithSith(CanViewMixin, SingleObjectMixin, View):
self.request, self.request,
_("You can't buy a refilling with sith money"), _("You can't buy a refilling with sith money"),
) )
return redirect("eboutic:main") return redirect("eboutic:payment_result", "failure")
eboutic = Counter.objects.get(type="EBOUTIC") eboutic = get_eboutic()
sales = basket.generate_sales(eboutic, basket.user, "SITH_ACCOUNT") sales = basket.generate_sales(eboutic, basket.user, "SITH_ACCOUNT")
try: try:
with transaction.atomic(): with transaction.atomic():
@ -260,6 +260,8 @@ class EbouticPayWithSith(CanViewMixin, SingleObjectMixin, View):
return redirect("eboutic:payment_result", "success") return redirect("eboutic:payment_result", "success")
except DatabaseError as e: except DatabaseError as e:
sentry_sdk.capture_exception(e) sentry_sdk.capture_exception(e)
except ValidationError as e:
messages.error(self.request, e.message)
return redirect("eboutic:payment_result", "failure") return redirect("eboutic:payment_result", "failure")