2017-04-24 15:51:12 +00:00
|
|
|
#
|
|
|
|
# Copyright 2016,2017
|
|
|
|
# - Skia <skia@libskia.so>
|
2022-09-25 19:29:42 +00:00
|
|
|
# - Maréchal <thgirod@hotmail.com
|
2017-04-24 15:51:12 +00:00
|
|
|
#
|
|
|
|
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
|
|
|
|
# http://ae.utbm.fr.
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
|
|
# the terms of the GNU General Public License a published by the Free Software
|
|
|
|
# Foundation; either version 3 of the License, or (at your option) any later
|
|
|
|
# version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
|
|
# details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License along with
|
|
|
|
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
|
|
|
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#
|
|
|
|
#
|
2017-05-01 17:39:13 +00:00
|
|
|
import base64
|
2022-09-25 19:29:42 +00:00
|
|
|
import json
|
2017-05-01 17:39:13 +00:00
|
|
|
import urllib
|
2024-10-15 09:36:26 +00:00
|
|
|
from typing import TYPE_CHECKING
|
2017-04-24 15:51:12 +00:00
|
|
|
|
2024-06-26 13:29:05 +00:00
|
|
|
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
|
|
|
from cryptography.hazmat.primitives.hashes import SHA1
|
|
|
|
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
2022-09-25 19:29:42 +00:00
|
|
|
from django.conf import settings
|
|
|
|
from django.db.models import Max
|
2016-07-21 23:19:50 +00:00
|
|
|
from django.test import TestCase
|
2019-10-06 11:28:56 +00:00
|
|
|
from django.urls import reverse
|
2017-05-01 17:39:13 +00:00
|
|
|
|
|
|
|
from core.models import User
|
2024-06-24 11:07:36 +00:00
|
|
|
from counter.models import Counter, Customer, Product, Selling
|
2024-07-27 22:09:39 +00:00
|
|
|
from eboutic.models import Basket, BasketItem
|
2017-06-12 07:50:08 +00:00
|
|
|
|
2024-10-15 09:36:26 +00:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
|
|
|
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2024-07-23 22:39:26 +00:00
|
|
|
class TestEboutic(TestCase):
|
2022-11-28 16:03:46 +00:00
|
|
|
@classmethod
|
|
|
|
def setUpTestData(cls):
|
2024-06-26 17:10:24 +00:00
|
|
|
cls.barbar = Product.objects.get(code="BARB")
|
|
|
|
cls.refill = Product.objects.get(code="15REFILL")
|
|
|
|
cls.cotis = Product.objects.get(code="1SCOTIZ")
|
|
|
|
cls.eboutic = Counter.objects.get(name="Eboutic")
|
|
|
|
cls.skia = User.objects.get(username="skia")
|
|
|
|
cls.subscriber = User.objects.get(username="subscriber")
|
|
|
|
cls.old_subscriber = User.objects.get(username="old_subscriber")
|
|
|
|
cls.public = User.objects.get(username="public")
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-11-28 16:03:46 +00:00
|
|
|
def get_busy_basket(self, user) -> Basket:
|
2024-07-12 07:34:16 +00:00
|
|
|
"""Create and return a basket with 3 barbar and 1 cotis in it.
|
|
|
|
|
|
|
|
Edit the client session to store the basket id in it.
|
2022-09-25 19:29:42 +00:00
|
|
|
"""
|
|
|
|
session = self.client.session
|
|
|
|
basket = Basket.objects.create(user=user)
|
|
|
|
session["basket_id"] = basket.id
|
|
|
|
session.save()
|
2024-07-27 22:09:39 +00:00
|
|
|
BasketItem.from_product(self.barbar, 3, basket).save()
|
|
|
|
BasketItem.from_product(self.cotis, 1, basket).save()
|
2022-09-25 19:29:42 +00:00
|
|
|
return basket
|
|
|
|
|
2022-11-28 16:03:46 +00:00
|
|
|
def generate_bank_valid_answer(self) -> str:
|
|
|
|
basket = Basket.from_session(self.client.session)
|
|
|
|
basket_id = basket.id
|
2024-07-27 22:09:39 +00:00
|
|
|
amount = int(basket.total * 100)
|
2022-11-28 16:03:46 +00:00
|
|
|
query = f"Amount={amount}&BasketID={basket_id}&Auto=42&Error=00000"
|
2024-06-26 13:29:05 +00:00
|
|
|
with open("./eboutic/tests/private_key.pem", "br") as f:
|
2017-05-01 17:39:13 +00:00
|
|
|
PRIVKEY = f.read()
|
|
|
|
with open("./eboutic/tests/public_key.pem") as f:
|
|
|
|
settings.SITH_EBOUTIC_PUB_KEY = f.read()
|
2024-06-26 13:29:05 +00:00
|
|
|
key: RSAPrivateKey = load_pem_private_key(PRIVKEY, None)
|
|
|
|
sig = key.sign(query.encode("utf-8"), PKCS1v15(), SHA1())
|
2017-05-01 17:39:13 +00:00
|
|
|
b64sig = base64.b64encode(sig).decode("ascii")
|
|
|
|
|
2018-10-04 19:29:19 +00:00
|
|
|
url = reverse("eboutic:etransation_autoanswer") + "?%s&Sig=%s" % (
|
|
|
|
query,
|
|
|
|
urllib.parse.quote_plus(b64sig),
|
|
|
|
)
|
2022-11-28 16:03:46 +00:00
|
|
|
return url
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-09-25 19:29:42 +00:00
|
|
|
def test_buy_with_sith_account(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.subscriber)
|
2022-09-25 19:29:42 +00:00
|
|
|
self.subscriber.customer.amount = 100 # give money before test
|
|
|
|
self.subscriber.customer.save()
|
|
|
|
basket = self.get_busy_basket(self.subscriber)
|
2024-07-27 22:09:39 +00:00
|
|
|
amount = basket.total
|
2022-09-25 19:29:42 +00:00
|
|
|
response = self.client.post(reverse("eboutic:pay_with_sith"))
|
|
|
|
self.assertRedirects(response, "/eboutic/pay/success/")
|
|
|
|
new_balance = Customer.objects.get(user=self.subscriber).amount
|
2024-06-26 17:10:24 +00:00
|
|
|
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()
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-09-25 19:29:42 +00:00
|
|
|
def test_buy_with_sith_account_no_money(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.subscriber)
|
2022-09-25 19:29:42 +00:00
|
|
|
basket = self.get_busy_basket(self.subscriber)
|
2024-07-27 22:09:39 +00:00
|
|
|
initial = basket.total - 1 # just not enough to complete the sale
|
2022-09-25 19:29:42 +00:00
|
|
|
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
|
2024-06-26 17:10:24 +00:00
|
|
|
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()
|
2022-09-25 19:29:42 +00:00
|
|
|
|
|
|
|
def test_submit_basket(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.subscriber)
|
2024-06-24 09:56:38 +00:00
|
|
|
self.client.cookies["basket_items"] = """[
|
2022-09-25 19:29:42 +00:00
|
|
|
{"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28},
|
|
|
|
{"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7}
|
|
|
|
]"""
|
2022-11-28 16:03:46 +00:00
|
|
|
response = self.client.get(reverse("eboutic:command"))
|
2024-06-26 17:10:24 +00:00
|
|
|
assert response.status_code == 200
|
2022-09-25 19:29:42 +00:00
|
|
|
self.assertInHTML(
|
|
|
|
"<tr><td>Cotis 2 semestres</td><td>1</td><td>28.00 €</td></tr>",
|
|
|
|
response.content.decode(),
|
|
|
|
)
|
|
|
|
self.assertInHTML(
|
|
|
|
"<tr><td>Barbar</td><td>3</td><td>1.70 €</td></tr>",
|
|
|
|
response.content.decode(),
|
|
|
|
)
|
2024-06-26 17:10:24 +00:00
|
|
|
assert "basket_id" in self.client.session
|
2022-09-25 19:29:42 +00:00
|
|
|
basket = Basket.objects.get(id=self.client.session["basket_id"])
|
2024-06-26 17:10:24 +00:00
|
|
|
assert basket.items.count() == 2
|
2022-09-25 19:29:42 +00:00
|
|
|
barbar = basket.items.filter(product_name="Barbar").first()
|
2024-06-26 17:10:24 +00:00
|
|
|
assert barbar is not None
|
|
|
|
assert barbar.quantity == 3
|
2022-09-25 19:29:42 +00:00
|
|
|
cotis = basket.items.filter(product_name="Cotis 2 semestres").first()
|
2024-06-26 17:10:24 +00:00
|
|
|
assert cotis is not None
|
|
|
|
assert cotis.quantity == 1
|
2024-07-27 22:09:39 +00:00
|
|
|
assert basket.total == 3 * 1.7 + 28
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-09-25 19:29:42 +00:00
|
|
|
def test_submit_empty_basket(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.subscriber)
|
2022-09-25 19:29:42 +00:00
|
|
|
self.client.cookies["basket_items"] = "[]"
|
2022-11-28 16:03:46 +00:00
|
|
|
response = self.client.get(reverse("eboutic:command"))
|
2022-09-25 19:29:42 +00:00
|
|
|
self.assertRedirects(response, "/eboutic/")
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-09-25 19:29:42 +00:00
|
|
|
def test_submit_invalid_basket(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.subscriber)
|
2022-09-25 19:29:42 +00:00
|
|
|
max_id = Product.objects.aggregate(res=Max("id"))["res"]
|
2024-06-24 09:56:38 +00:00
|
|
|
self.client.cookies["basket_items"] = f"""[
|
2022-09-25 19:29:42 +00:00
|
|
|
{{"id": {max_id + 1}, "name": "", "quantity": 1, "unit_price": 28}}
|
|
|
|
]"""
|
2022-11-28 16:03:46 +00:00
|
|
|
response = self.client.get(reverse("eboutic:command"))
|
2024-06-26 17:10:24 +00:00
|
|
|
cookie = self.client.cookies["basket_items"].OutputString()
|
2024-07-27 22:09:39 +00:00
|
|
|
assert 'basket_items="[]"' in cookie
|
2024-06-26 17:10:24 +00:00
|
|
|
assert "Path=/eboutic" in cookie
|
2022-09-25 19:29:42 +00:00
|
|
|
self.assertRedirects(response, "/eboutic/")
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-09-25 19:29:42 +00:00
|
|
|
def test_submit_basket_illegal_quantity(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.subscriber)
|
2024-06-24 09:56:38 +00:00
|
|
|
self.client.cookies["basket_items"] = """[
|
2022-09-25 19:29:42 +00:00
|
|
|
{"id": 4, "name": "Barbar", "quantity": -1, "unit_price": 1.7}
|
|
|
|
]"""
|
2022-11-28 16:03:46 +00:00
|
|
|
response = self.client.get(reverse("eboutic:command"))
|
2022-09-25 19:29:42 +00:00
|
|
|
self.assertRedirects(response, "/eboutic/")
|
2019-03-15 00:48:42 +00:00
|
|
|
|
2022-09-25 19:29:42 +00:00
|
|
|
def test_buy_subscribe_product_with_credit_card(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.old_subscriber)
|
2022-09-25 19:29:42 +00:00
|
|
|
response = self.client.get(
|
|
|
|
reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id})
|
2019-03-15 00:48:42 +00:00
|
|
|
)
|
2024-06-26 17:10:24 +00:00
|
|
|
assert "Non cotisant" in str(response.content)
|
2024-06-24 09:56:38 +00:00
|
|
|
self.client.cookies["basket_items"] = """[
|
2022-09-25 19:29:42 +00:00
|
|
|
{"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28}
|
|
|
|
]"""
|
2022-11-28 16:03:46 +00:00
|
|
|
response = self.client.get(reverse("eboutic:command"))
|
2022-09-25 19:29:42 +00:00
|
|
|
self.assertInHTML(
|
|
|
|
"<tr><td>Cotis 2 semestres</td><td>1</td><td>28.00 €</td></tr>",
|
|
|
|
response.content.decode(),
|
2019-03-15 00:48:42 +00:00
|
|
|
)
|
2022-09-25 19:29:42 +00:00
|
|
|
basket = Basket.objects.get(id=self.client.session["basket_id"])
|
2024-06-26 17:10:24 +00:00
|
|
|
assert basket.items.count() == 1
|
2022-11-28 16:03:46 +00:00
|
|
|
response = self.client.get(self.generate_bank_valid_answer())
|
2024-06-26 17:10:24 +00:00
|
|
|
assert response.status_code == 200
|
|
|
|
assert response.content.decode("utf-8") == "Payment successful"
|
2022-09-25 19:29:42 +00:00
|
|
|
|
|
|
|
subscriber = User.objects.get(id=self.old_subscriber.id)
|
2024-06-26 17:10:24 +00:00
|
|
|
assert subscriber.subscriptions.count() == 2
|
2022-09-25 19:29:42 +00:00
|
|
|
sub = subscriber.subscriptions.order_by("-subscription_end").first()
|
2024-06-26 17:10:24 +00:00
|
|
|
assert sub.is_valid_now()
|
|
|
|
assert sub.member == subscriber
|
|
|
|
assert sub.subscription_type == "deux-semestres"
|
|
|
|
assert sub.location == "EBOUTIC"
|
2019-03-15 00:48:42 +00:00
|
|
|
|
2017-05-01 17:39:13 +00:00
|
|
|
def test_buy_refill_product_with_credit_card(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.subscriber)
|
2022-09-25 19:29:42 +00:00
|
|
|
# basket contains 1 refill item worth 15€
|
|
|
|
self.client.cookies["basket_items"] = json.dumps(
|
|
|
|
[{"id": 3, "name": "Rechargement 15 €", "quantity": 1, "unit_price": 15}]
|
2018-10-04 19:29:19 +00:00
|
|
|
)
|
2022-09-25 19:29:42 +00:00
|
|
|
initial_balance = self.subscriber.customer.amount
|
2022-11-28 16:03:46 +00:00
|
|
|
self.client.get(reverse("eboutic:command"))
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-11-28 16:03:46 +00:00
|
|
|
url = self.generate_bank_valid_answer()
|
|
|
|
response = self.client.get(url)
|
2024-06-26 17:10:24 +00:00
|
|
|
assert response.status_code == 200
|
|
|
|
assert response.content.decode() == "Payment successful"
|
2022-09-25 19:29:42 +00:00
|
|
|
new_balance = Customer.objects.get(user=self.subscriber).amount
|
2024-06-26 17:10:24 +00:00
|
|
|
assert new_balance == initial_balance + 15
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-09-25 19:29:42 +00:00
|
|
|
def test_alter_basket_after_submission(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.subscriber)
|
2022-09-25 19:29:42 +00:00
|
|
|
self.client.cookies["basket_items"] = json.dumps(
|
|
|
|
[{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}]
|
2018-10-04 19:29:19 +00:00
|
|
|
)
|
2022-11-28 16:03:46 +00:00
|
|
|
self.client.get(reverse("eboutic:command"))
|
|
|
|
et_answer_url = self.generate_bank_valid_answer()
|
2022-09-25 19:29:42 +00:00
|
|
|
self.client.cookies["basket_items"] = json.dumps(
|
|
|
|
[ # alter basket
|
|
|
|
{"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7}
|
|
|
|
]
|
2018-10-04 19:29:19 +00:00
|
|
|
)
|
2022-11-28 16:03:46 +00:00
|
|
|
self.client.get(reverse("eboutic:command"))
|
|
|
|
response = self.client.get(et_answer_url)
|
2024-06-26 17:10:24 +00:00
|
|
|
assert response.status_code == 500
|
2024-07-04 08:19:24 +00:00
|
|
|
msg = (
|
|
|
|
"Basket processing failed with error: "
|
|
|
|
"SuspiciousOperation('Basket total and amount do not match'"
|
2018-10-04 19:29:19 +00:00
|
|
|
)
|
2024-07-04 08:19:24 +00:00
|
|
|
assert msg in response.content.decode("utf-8")
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-09-25 19:29:42 +00:00
|
|
|
def test_buy_simple_product_with_credit_card(self):
|
2024-06-26 17:10:24 +00:00
|
|
|
self.client.force_login(self.subscriber)
|
2022-09-25 19:29:42 +00:00
|
|
|
self.client.cookies["basket_items"] = json.dumps(
|
|
|
|
[{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}]
|
2018-10-04 19:29:19 +00:00
|
|
|
)
|
2022-11-28 16:03:46 +00:00
|
|
|
self.client.get(reverse("eboutic:command"))
|
|
|
|
et_answer_url = self.generate_bank_valid_answer()
|
|
|
|
response = self.client.get(et_answer_url)
|
2024-06-26 17:10:24 +00:00
|
|
|
assert response.status_code == 200
|
|
|
|
assert response.content.decode("utf-8") == "Payment successful"
|
2017-05-01 17:39:13 +00:00
|
|
|
|
2022-09-25 19:29:42 +00:00
|
|
|
selling = (
|
|
|
|
Selling.objects.filter(customer=self.subscriber.customer)
|
|
|
|
.order_by("-date")
|
|
|
|
.first()
|
|
|
|
)
|
2024-06-26 17:10:24 +00:00
|
|
|
assert selling.payment_method == "CARD"
|
|
|
|
assert selling.quantity == 1
|
|
|
|
assert selling.unit_price == self.barbar.selling_price
|
|
|
|
assert selling.counter.type == "EBOUTIC"
|
|
|
|
assert selling.product == self.barbar
|