mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-31 07:49:57 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			437 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			437 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from api.tests import TestAPI
 | |
| from member.models import Club, Membership
 | |
| from django.contrib.auth.models import User
 | |
| from django.contrib.contenttypes.models import ContentType
 | |
| from django.test import TestCase
 | |
| from django.urls import reverse
 | |
| from django.utils import timezone
 | |
| from permission.models import Role
 | |
| 
 | |
| from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \
 | |
|     TransactionTemplateViewSet, TransactionViewSet
 | |
| from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
 | |
|     MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note
 | |
| 
 | |
| 
 | |
| class TestTransactions(TestCase):
 | |
|     fixtures = ('initial', )
 | |
| 
 | |
|     def setUp(self) -> None:
 | |
|         self.user = User.objects.create_superuser(
 | |
|             username="toto",
 | |
|             password="totototo",
 | |
|             email="toto@example.com",
 | |
|         )
 | |
| 
 | |
|         sess = self.client.session
 | |
|         sess["permission_mask"] = 42
 | |
|         sess.save()
 | |
|         self.client.force_login(self.user)
 | |
| 
 | |
|         membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user)
 | |
|         membership.roles.add(Role.objects.get(name="Respo info"))
 | |
|         membership.save()
 | |
|         Membership.objects.create(club=Club.objects.get(name="Kfet"), user=self.user)
 | |
|         self.user.note.refresh_from_db()
 | |
| 
 | |
|         self.second_user = User.objects.create(
 | |
|             username="toto2",
 | |
|         )
 | |
|         # Non superusers have no note until the registration get validated
 | |
|         NoteUser.objects.create(user=self.second_user)
 | |
| 
 | |
|         self.club = Club.objects.create(
 | |
|             name="clubtoto",
 | |
|         )
 | |
| 
 | |
|         self.transaction = Transaction.objects.create(
 | |
|             source=self.second_user.note,
 | |
|             destination=self.user.note,
 | |
|             amount=4200,
 | |
|             reason="Test transaction",
 | |
|         )
 | |
|         self.user.note.refresh_from_db()
 | |
|         self.second_user.note.refresh_from_db()
 | |
| 
 | |
|         self.category = TemplateCategory.objects.create(name="Test")
 | |
|         self.template = TransactionTemplate.objects.create(
 | |
|             name="Test",
 | |
|             destination=self.club.note,
 | |
|             category=self.category,
 | |
|             amount=100,
 | |
|             description="Test template",
 | |
|         )
 | |
| 
 | |
|     def test_admin_pages(self):
 | |
|         """
 | |
|         Load some admin pages to check that they render successfully.
 | |
|         """
 | |
|         response = self.client.get(reverse("admin:index") + "note/note/")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get(reverse("admin:index") + "note/transaction/")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get(reverse("admin:index") + "note/transaction/" + str(self.transaction.pk) + "/change/")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get(reverse("admin:index") + "note/transaction/add/?ct_id="
 | |
|                                    + str(ContentType.objects.get_for_model(Transaction).id))
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get(reverse("admin:index") + "note/transaction/add/?ct_id="
 | |
|                                    + str(ContentType.objects.get_for_model(RecurrentTransaction).id))
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get(reverse("admin:index") + "note/transaction/add/?ct_id="
 | |
|                                    + str(ContentType.objects.get_for_model(MembershipTransaction).id))
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get(reverse("admin:index") + "note/transaction/add/?ct_id="
 | |
|                                    + str(ContentType.objects.get_for_model(SpecialTransaction).id))
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get(reverse("admin:index") + "note/transactiontemplate/")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get(reverse("admin:index") + "note/templatecategory/")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
| 
 | |
|     def test_render_transfer_page(self):
 | |
|         response = self.client.get(reverse("note:transfer"))
 | |
|         self.assertEqual(response.status_code, 200)
 | |
| 
 | |
|     def test_transfer_api(self):
 | |
|         old_user_balance = self.user.note.balance
 | |
|         old_second_user_balance = self.second_user.note.balance
 | |
|         quantity = 3
 | |
|         amount = 314
 | |
|         total = quantity * amount
 | |
|         response = self.client.post("/api/note/transaction/transaction/", data=dict(
 | |
|             quantity=quantity,
 | |
|             amount=amount,
 | |
|             reason="Transaction through API",
 | |
|             valid=True,
 | |
|             polymorphic_ctype=ContentType.objects.get_for_model(Transaction).id,
 | |
|             resourcetype="Transaction",
 | |
|             source=self.user.note.id,
 | |
|             source_alias=self.user.username,
 | |
|             destination=self.second_user.note.id,
 | |
|             destination_alias=self.second_user.username,
 | |
|         ))
 | |
|         self.assertEqual(response.status_code, 201)  # 201 = Created
 | |
|         self.assertTrue(Transaction.objects.filter(reason="Transaction through API").exists())
 | |
| 
 | |
|         self.user.note.refresh_from_db()
 | |
|         self.second_user.note.refresh_from_db()
 | |
| 
 | |
|         self.assertTrue(self.user.note.balance == old_user_balance - total)
 | |
|         self.assertTrue(self.second_user.note.balance == old_second_user_balance + total)
 | |
| 
 | |
|         self.test_render_transfer_page()
 | |
| 
 | |
|     def test_credit_api(self):
 | |
|         old_user_balance = self.user.note.balance
 | |
|         amount = 4242
 | |
|         special_type = NoteSpecial.objects.first()
 | |
|         response = self.client.post("/api/note/transaction/transaction/", data=dict(
 | |
|             quantity=1,
 | |
|             amount=amount,
 | |
|             reason="Credit through API",
 | |
|             valid=True,
 | |
|             polymorphic_ctype=ContentType.objects.get_for_model(SpecialTransaction).id,
 | |
|             resourcetype="SpecialTransaction",
 | |
|             source=special_type.id,
 | |
|             source_alias=str(special_type),
 | |
|             destination=self.user.note.id,
 | |
|             destination_alias=self.user.username,
 | |
|             last_name="TOTO",
 | |
|             first_name="Toto",
 | |
|         ))
 | |
| 
 | |
|         self.assertEqual(response.status_code, 201)  # 201 = Created
 | |
|         self.assertTrue(Transaction.objects.filter(reason="Credit through API").exists())
 | |
|         self.user.note.refresh_from_db()
 | |
|         self.assertTrue(self.user.note.balance == old_user_balance + amount)
 | |
| 
 | |
|         self.test_render_transfer_page()
 | |
| 
 | |
|     def test_debit_api(self):
 | |
|         old_user_balance = self.user.note.balance
 | |
|         amount = 4242
 | |
|         special_type = NoteSpecial.objects.first()
 | |
|         response = self.client.post("/api/note/transaction/transaction/", data=dict(
 | |
|             quantity=1,
 | |
|             amount=amount,
 | |
|             reason="Debit through API",
 | |
|             valid=True,
 | |
|             polymorphic_ctype=ContentType.objects.get_for_model(SpecialTransaction).id,
 | |
|             resourcetype="SpecialTransaction",
 | |
|             source=self.user.note.id,
 | |
|             source_alias=self.user.username,
 | |
|             destination=special_type.id,
 | |
|             destination_alias=str(special_type),
 | |
|             last_name="TOTO",
 | |
|             first_name="Toto",
 | |
|         ))
 | |
|         self.assertEqual(response.status_code, 201)  # 201 = Created
 | |
|         self.assertTrue(Transaction.objects.filter(reason="Debit through API").exists())
 | |
|         self.user.note.refresh_from_db()
 | |
|         self.assertTrue(self.user.note.balance == old_user_balance - amount)
 | |
| 
 | |
|         self.test_render_transfer_page()
 | |
| 
 | |
|     def test_render_consos_page(self):
 | |
|         response = self.client.get(reverse("note:consos"))
 | |
|         self.assertEqual(response.status_code, 200)
 | |
| 
 | |
|     def test_consumption_api(self):
 | |
|         old_user_balance = self.user.note.balance
 | |
|         old_club_balance = self.club.note.balance
 | |
|         quantity = 2
 | |
|         template = self.template
 | |
|         total = quantity * template.amount
 | |
|         response = self.client.post("/api/note/transaction/transaction/", data=dict(
 | |
|             quantity=quantity,
 | |
|             amount=template.amount,
 | |
|             reason="Consumption through API (" + template.name + ")",
 | |
|             valid=True,
 | |
|             polymorphic_ctype=ContentType.objects.get_for_model(RecurrentTransaction).id,
 | |
|             resourcetype="RecurrentTransaction",
 | |
|             source=self.user.note.id,
 | |
|             source_alias=self.user.username,
 | |
|             destination=self.club.note.id,
 | |
|             destination_alias=self.second_user.username,
 | |
|             template=template.id,
 | |
|         ))
 | |
|         self.assertEqual(response.status_code, 201)  # 201 = Created
 | |
|         self.assertTrue(Transaction.objects.filter(destination=self.club.note).exists())
 | |
| 
 | |
|         self.user.note.refresh_from_db()
 | |
|         self.club.note.refresh_from_db()
 | |
| 
 | |
|         self.assertTrue(self.user.note.balance == old_user_balance - total)
 | |
|         self.assertTrue(self.club.note.balance == old_club_balance + total)
 | |
| 
 | |
|         self.test_render_consos_page()
 | |
| 
 | |
|     def test_invalidate_transaction(self):
 | |
|         old_second_user_balance = self.second_user.note.balance
 | |
|         old_user_balance = self.user.note.balance
 | |
|         total = self.transaction.total
 | |
|         response = self.client.patch("/api/note/transaction/transaction/" + str(self.transaction.pk) + "/", data=dict(
 | |
|             valid=False,
 | |
|             resourcetype="Transaction",
 | |
|             invalidity_reason="Test invalidate",
 | |
|         ), content_type="application/json")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         self.assertTrue(Transaction.objects.filter(valid=False, invalidity_reason="Test invalidate").exists())
 | |
| 
 | |
|         self.second_user.note.refresh_from_db()
 | |
|         self.user.note.refresh_from_db()
 | |
| 
 | |
|         self.assertTrue(self.second_user.note.balance == old_second_user_balance + total)
 | |
|         self.assertTrue(self.user.note.balance == old_user_balance - total)
 | |
| 
 | |
|         self.test_render_transfer_page()
 | |
|         self.test_render_consos_page()
 | |
| 
 | |
|         # Now we check if we can revalidate
 | |
|         old_second_user_balance = self.second_user.note.balance
 | |
|         old_user_balance = self.user.note.balance
 | |
|         total = self.transaction.total
 | |
|         response = self.client.patch("/api/note/transaction/transaction/" + str(self.transaction.pk) + "/", data=dict(
 | |
|             valid=True,
 | |
|             resourcetype="Transaction",
 | |
|         ), content_type="application/json")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         self.assertTrue(Transaction.objects.filter(valid=True, pk=self.transaction.pk).exists())
 | |
| 
 | |
|         self.second_user.note.refresh_from_db()
 | |
|         self.user.note.refresh_from_db()
 | |
| 
 | |
|         self.assertTrue(self.second_user.note.balance == old_second_user_balance - total)
 | |
|         self.assertTrue(self.user.note.balance == old_user_balance + total)
 | |
| 
 | |
|         self.test_render_transfer_page()
 | |
|         self.test_render_consos_page()
 | |
| 
 | |
|     def test_render_template_list(self):
 | |
|         response = self.client.get(reverse("note:template_list") + "?search=test")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
| 
 | |
|     def test_render_template_create(self):
 | |
|         response = self.client.get(reverse("note:template_create"))
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.post(reverse("note:template_create"), data=dict(
 | |
|             name="Test create button",
 | |
|             destination=self.club.note.pk,
 | |
|             category=self.category.pk,
 | |
|             amount=4200,
 | |
|             description="We have created a button",
 | |
|             highlighted=True,
 | |
|             display=True,
 | |
|         ))
 | |
|         self.assertRedirects(response, reverse("note:template_list"), 302, 200)
 | |
|         self.assertTrue(TransactionTemplate.objects.filter(name="Test create button").exists())
 | |
| 
 | |
|     def test_render_template_update(self):
 | |
|         response = self.client.get(self.template.get_absolute_url())
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.post(self.template.get_absolute_url(), data=dict(
 | |
|             name="Test update button",
 | |
|             destination=self.club.note.pk,
 | |
|             category=self.category.pk,
 | |
|             amount=4200,
 | |
|             description="We have updated a button",
 | |
|             highlighted=True,
 | |
|             display=True,
 | |
|         ))
 | |
|         self.assertRedirects(response, reverse("note:template_list"), 302, 200)
 | |
|         self.assertTrue(TransactionTemplate.objects.filter(name="Test update button", pk=self.template.pk).exists())
 | |
| 
 | |
|         # Check that the price history renders properly
 | |
|         response = self.client.post(self.template.get_absolute_url(), data=dict(
 | |
|             name="Test price history",
 | |
|             destination=self.club.note.pk,
 | |
|             category=self.category.pk,
 | |
|             amount=4200,
 | |
|             description="We have updated a button",
 | |
|             highlighted=True,
 | |
|             display=True,
 | |
|         ))
 | |
|         self.assertRedirects(response, reverse("note:template_list"), 302, 200)
 | |
|         self.assertTrue(TransactionTemplate.objects.filter(name="Test price history", pk=self.template.pk).exists())
 | |
|         response = self.client.get(reverse("note:template_update", args=(self.template.pk,)))
 | |
|         self.assertEqual(response.status_code, 200)
 | |
| 
 | |
|     def test_render_search_transactions(self):
 | |
|         response = self.client.get(reverse("note:transactions", args=(self.user.note.pk,)), data=dict(
 | |
|             source=self.second_user.note.alias.first().id,
 | |
|             destination=self.user.note.alias.first().id,
 | |
|             type=[ContentType.objects.get_for_model(Transaction).id],
 | |
|             reason="test",
 | |
|             valid=True,
 | |
|             amount_gte=0,
 | |
|             amount_lte=42424242,
 | |
|             created_after="2000-01-01 00:00",
 | |
|             created_before="2042-12-31 21:42",
 | |
|         ))
 | |
|         self.assertEqual(response.status_code, 200)
 | |
| 
 | |
|     def test_delete_transaction(self):
 | |
|         # Transactions can't be deleted with a normal usage, but it is possible through the admin interface.
 | |
|         old_second_user_balance = self.second_user.note.balance
 | |
|         old_user_balance = self.user.note.balance
 | |
|         total = self.transaction.total
 | |
| 
 | |
|         self.transaction.delete()
 | |
|         self.second_user.note.refresh_from_db()
 | |
|         self.user.note.refresh_from_db()
 | |
| 
 | |
|         self.assertTrue(self.second_user.note.balance == old_second_user_balance + total)
 | |
|         self.assertTrue(self.user.note.balance == old_user_balance - total)
 | |
| 
 | |
|     def test_calculate_last_negative_duration(self):
 | |
|         self.assertIsNone(self.user.note.last_negative_duration)
 | |
|         self.assertIsNotNone(self.second_user.note.last_negative_duration)
 | |
|         self.assertIsNone(self.club.note.last_negative_duration)
 | |
| 
 | |
|         Transaction.objects.create(
 | |
|             source=self.club.note,
 | |
|             destination=self.user.note,
 | |
|             amount=2 * self.club.note.balance + 100,
 | |
|             reason="Club balance is negative",
 | |
|         )
 | |
| 
 | |
|         self.club.note.refresh_from_db()
 | |
|         self.assertIsNotNone(self.club.note.last_negative_duration)
 | |
| 
 | |
|     def test_api_search(self):
 | |
|         response = self.client.get("/api/note/note/")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get("/api/note/alias/?alias=.*")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get("/api/note/consumer/")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get("/api/note/transaction/transaction/")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         response = self.client.get("/api/note/transaction/template/")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
| 
 | |
|     def test_api_alias(self):
 | |
|         response = self.client.post("/api/note/alias/", data=dict(
 | |
|             name="testalias",
 | |
|             note=self.user.note.id,
 | |
|         ))
 | |
|         self.assertEqual(response.status_code, 201)
 | |
|         self.assertTrue(Alias.objects.filter(name="testalias").exists())
 | |
|         alias = Alias.objects.get(name="testalias")
 | |
|         response = self.client.patch("/api/note/alias/" + str(alias.pk) + "/", dict(name="test_updated_alias"),
 | |
|                                      content_type="application/json")
 | |
|         self.assertEqual(response.status_code, 200)
 | |
|         self.assertTrue(Alias.objects.filter(name="test_updated_alias").exists())
 | |
|         response = self.client.delete("/api/note/alias/" + str(alias.pk) + "/")
 | |
|         self.assertEqual(response.status_code, 204)
 | |
| 
 | |
| 
 | |
| class TestNoteAPI(TestAPI):
 | |
|     def setUp(self) -> None:
 | |
|         super().setUp()
 | |
| 
 | |
|         membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user)
 | |
|         membership.roles.add(Role.objects.get(name="Respo info"))
 | |
|         membership.save()
 | |
|         Membership.objects.create(club=Club.objects.get(name="Kfet"), user=self.user)
 | |
|         self.user.note.last_negative = timezone.now()
 | |
|         self.user.note.save()
 | |
| 
 | |
|         self.transaction = Transaction.objects.create(
 | |
|             source=Note.objects.first(),
 | |
|             destination=self.user.note,
 | |
|             amount=4200,
 | |
|             reason="Test transaction",
 | |
|         )
 | |
|         self.user.note.refresh_from_db()
 | |
|         Alias.objects.create(note=self.user.note, name="I am a ¢omplex alias")
 | |
| 
 | |
|         self.category = TemplateCategory.objects.create(name="Test")
 | |
|         self.template = TransactionTemplate.objects.create(
 | |
|             name="Test",
 | |
|             destination=Club.objects.get(name="BDE").note,
 | |
|             category=self.category,
 | |
|             amount=100,
 | |
|             description="Test template",
 | |
|         )
 | |
| 
 | |
|     def test_alias_api(self):
 | |
|         """
 | |
|         Load Alias API page and test all filters and permissions
 | |
|         """
 | |
|         self.check_viewset(AliasViewSet, "/api/note/alias/")
 | |
| 
 | |
|     def test_consumer_api(self):
 | |
|         """
 | |
|         Load Consumer API page and test all filters and permissions
 | |
|         """
 | |
|         self.check_viewset(ConsumerViewSet, "/api/note/consumer/")
 | |
| 
 | |
|     def test_note_api(self):
 | |
|         """
 | |
|         Load Note API page and test all filters and permissions
 | |
|         """
 | |
|         self.check_viewset(NotePolymorphicViewSet, "/api/note/note/")
 | |
| 
 | |
|     def test_template_category_api(self):
 | |
|         """
 | |
|         Load TemplateCategory API page and test all filters and permissions
 | |
|         """
 | |
|         self.check_viewset(TemplateCategoryViewSet, "/api/note/transaction/category/")
 | |
| 
 | |
|     def test_transaction_template_api(self):
 | |
|         """
 | |
|         Load TemplateTemplate API page and test all filters and permissions
 | |
|         """
 | |
|         self.check_viewset(TransactionTemplateViewSet, "/api/note/transaction/template/")
 | |
| 
 | |
|     def test_transaction_api(self):
 | |
|         """
 | |
|         Load Transaction API page and test all filters and permissions
 | |
|         """
 | |
|         self.check_viewset(TransactionViewSet, "/api/note/transaction/transaction/")
 |