From 207af441a03a82fb75d3307b30229dde0ec83e11 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Sat, 24 Feb 2024 23:05:21 +0100 Subject: [PATCH] Add payment interface tests Signed-off-by: Emmy D'Anello --- participation/tests.py | 362 +++++++++++++++++++++++++++++++++++++++- registration/apps.py | 10 +- registration/forms.py | 3 + registration/models.py | 2 +- registration/signals.py | 10 ++ 5 files changed, 380 insertions(+), 7 deletions(-) diff --git a/participation/tests.py b/participation/tests.py index 8750f8b..919dc87 100644 --- a/participation/tests.py +++ b/participation/tests.py @@ -4,10 +4,11 @@ from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site +from django.core.files.uploadedfile import SimpleUploadedFile from django.core.management import call_command from django.test import TestCase from django.urls import reverse -from registration.models import CoachRegistration, StudentRegistration +from registration.models import CoachRegistration, Payment, StudentRegistration from .models import Participation, Team, Tournament @@ -515,6 +516,365 @@ class TestStudentParticipation(TestCase): self.assertEqual(resp.status_code, 403) +class TestPayment(TestCase): + """ + Tests that are relative to a payment + """ + + def setUp(self): + self.superuser = User.objects.create_superuser( + username="admin", + email="admin@example.com", + password="admin", + ) + self.tournament = Tournament.objects.create( + name="France", + place="Here", + price=21, + ) + self.team = Team.objects.create( + name="Super team", + trigram="AAA", + access_code="azerty", + ) + self.user = User.objects.create( + first_name="Toto", + last_name="Toto", + email="toto@example.com", + password="toto", + ) + StudentRegistration.objects.create( + user=self.user, + team=self.team, + student_class=12, + address="1 Rue de Rivoli", + zip_code=75001, + city="Paris", + school="Earth", + give_contact_to_animath=True, + email_confirmed=True, + ) + self.second_user = User.objects.create( + first_name="Lalala", + last_name="Lalala", + email="lalala@example.com", + password="lalala", + ) + StudentRegistration.objects.create( + user=self.second_user, + team=self.team, + student_class=11, + address="1 Rue de Rivoli", + zip_code=75001, + city="Paris", + school="Moon", + give_contact_to_animath=True, + email_confirmed=True, + ) + self.coach = User.objects.create( + first_name="Coach", + last_name="Coach", + email="coach@example.com", + password="coach", + ) + CoachRegistration.objects.create( + user=self.coach, + team=self.team, + address="1 Rue de Rivoli", + zip_code=75001, + city="Paris", + ) + + self.team.participation.tournament = self.tournament + self.team.participation.valid = True + self.team.participation.save() + self.client.force_login(self.user) + + def test_check_payments_exists(self): + """ + Check that users in a validated team have an invalid payment, but not for the final, + and that coaches are not concerned. + """ + self.assertTrue(Payment.objects.filter(final=False, valid=False, type='', + registrations=self.user.registration).exists()) + self.assertTrue(Payment.objects.filter(final=False, valid=False, type='', + registrations=self.second_user.registration).exists()) + self.assertFalse(Payment.objects.filter(final=False, valid=False, type='', + registrations=self.coach.registration).exists()) + + self.assertFalse(Payment.objects.filter(final=True, valid=False, type='', + registrations=self.user.registration).exists()) + self.assertFalse(Payment.objects.filter(final=True, valid=False, type='', + registrations=self.second_user.registration).exists()) + self.assertFalse(Payment.objects.filter(final=True, valid=False, type='', + registrations=self.coach.registration).exists()) + + def test_load_payment_page(self): + """ + Ensure that the payment page loads correctly. + """ + response = self.client.get(reverse('participation:team_detail', args=(self.team.pk,))) + self.assertEquals(response.status_code, 200) + + response = self.client.get(reverse('registration:user_detail', args=(self.user.pk,))) + self.assertEquals(response.status_code, 200) + + response = self.client.get(reverse('participation:tournament_payments', args=(self.tournament.pk,))) + self.assertEquals(response.status_code, 403) + + payment = Payment.objects.get(registrations=self.user.registration, final=False) + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + def test_bank_transfer_payment(self): + """ + Try to send a bank transfer. + """ + payment = Payment.objects.get(registrations=self.user.registration, final=False) + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "bank_transfer", + 'additional_information': "This is a bank transfer"}) + self.assertEquals(response.status_code, 200) + self.assertFormError(response.context['form'], 'receipt', + ["This field is required.", "You must upload your receipt."]) + payment.refresh_from_db() + self.assertFalse(payment.valid) + self.assertEquals(payment.type, "") + + # README is not a valid PDF file + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "bank_transfer", + 'additional_information': "This is a bank transfer", + 'receipt': open("README.md", "rb")}) + self.assertEquals(response.status_code, 200) + self.assertFormError(response.context['form'], 'receipt', + ["The uploaded file must be a PDF, PNG of JPEG file."]) + self.assertFalse(payment.valid) + self.assertEquals(payment.type, "") + + # Don't send too large files + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "bank_transfer", + 'additional_information': "This is a bank transfer", + 'receipt': SimpleUploadedFile( + "file.pdf", + content=int(0).to_bytes(2000001, "big"), + content_type="application/pdf"), + }) + self.assertEquals(response.status_code, 200) + self.assertFormError(response.context['form'], 'receipt', + ["The uploaded file size must be under 2 Mo."]) + self.assertFalse(payment.valid) + self.assertEquals(payment.type, "") + + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "bank_transfer", + 'additional_information': "This is a bank transfer", + 'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")}) + self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200) + payment.refresh_from_db() + self.assertIsNone(payment.valid) + self.assertEquals(payment.type, "bank_transfer") + self.assertEquals(payment.additional_information, "This is a bank transfer") + self.assertIsNotNone(payment.receipt) + + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + def test_scholarship(self): + """ + Try to don't pay because of a scholarship. + """ + payment = Payment.objects.get(registrations=self.user.registration, final=False) + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "scholarship", + 'additional_information': "I don't have to pay because I have a scholarship"}) + self.assertEquals(response.status_code, 200) + self.assertFormError(response.context['form'], 'receipt', + ["This field is required.", "You must upload your receipt."]) + payment.refresh_from_db() + self.assertFalse(payment.valid) + self.assertEquals(payment.type, "") + self.assertEquals(payment.amount, self.tournament.price) + + # README is not a valid PDF file + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "scholarship", + 'additional_information': "I don't have to pay because I have a scholarship", + 'receipt': open("README.md", "rb")}) + self.assertEquals(response.status_code, 200) + self.assertFormError(response.context['form'], 'receipt', + ["The uploaded file must be a PDF, PNG of JPEG file."]) + self.assertFalse(payment.valid) + self.assertEquals(payment.type, "") + self.assertEquals(payment.amount, self.tournament.price) + + # Don't send too large files + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "scholarship", + 'additional_information': "I don't have to pay because I have a scholarship", + 'receipt': SimpleUploadedFile( + "file.pdf", + content=int(0).to_bytes(2000001, "big"), + content_type="application/pdf"), + }) + self.assertEquals(response.status_code, 200) + self.assertFormError(response.context['form'], 'receipt', + ["The uploaded file size must be under 2 Mo."]) + self.assertFalse(payment.valid) + self.assertEquals(payment.type, "") + self.assertEquals(payment.amount, self.tournament.price) + + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "scholarship", + 'additional_information': "I don't have to pay because I have a scholarship", + 'receipt': open("tfjm/static/Fiche_sanitaire.pdf", "rb")}) + self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200) + payment.refresh_from_db() + self.assertIsNone(payment.valid) + self.assertEquals(payment.type, "scholarship") + self.assertEquals(payment.additional_information, "I don't have to pay because I have a scholarship") + self.assertIsNotNone(payment.receipt) + self.assertEquals(payment.amount, 0) + + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + def test_other(self): + """ + Try to send a different type of payment. + """ + payment = Payment.objects.get(registrations=self.user.registration, final=False) + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "other"}) + self.assertEquals(response.status_code, 200) + self.assertFormError(response.context['form'], 'additional_information', + ["This field is required."]) + payment.refresh_from_db() + self.assertFalse(payment.valid) + self.assertEquals(payment.type, "") + self.assertEquals(payment.amount, self.tournament.price) + + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'type': "other", + 'additional_information': "Why should I pay"}) + self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200) + payment.refresh_from_db() + self.assertIsNone(payment.valid) + self.assertEquals(payment.type, "other") + self.assertEquals(payment.additional_information, "Why should I pay") + self.assertIsNotNone(payment.receipt) + self.assertEquals(payment.amount, self.tournament.price) + + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + def test_group(self): + payment = Payment.objects.get(registrations=self.user.registration, final=False) + self.assertFalse(payment.grouped) + + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + response = self.client.get(reverse('registration:update_payment_group_mode', args=(payment.pk,))) + self.assertRedirects(response, reverse('registration:update_payment', args=(payment.pk,)), 302, 200) + payment.refresh_from_db() + self.assertTrue(payment.grouped) + self.assertEquals(Payment.objects.count(), 1) + self.assertIn(self.user.registration, payment.registrations.all()) + self.assertIn(self.second_user.registration, payment.registrations.all()) + self.assertEquals(payment.amount, 2 * self.tournament.price) + + def test_ungroup(self): + """ + Test to ungroup payments + """ + payment = Payment.objects.get(registrations=self.user.registration, final=False) + self.client.get(reverse('registration:update_payment_group_mode', args=(payment.pk,))) + payment.refresh_from_db() + self.assertTrue(payment.grouped) + + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + response = self.client.get(reverse('registration:update_payment_group_mode', args=(payment.pk,))) + self.assertRedirects(response, reverse('registration:update_payment', args=(payment.pk,)), 302, 200) + payment.refresh_from_db() + self.assertFalse(payment.grouped) + self.assertEquals(Payment.objects.count(), 2) + self.assertIn(self.user.registration, payment.registrations.all()) + self.assertNotIn(self.second_user.registration, payment.registrations.all()) + self.assertEquals(payment.amount, self.tournament.price) + + def test_group_forbidden(self): + """ + Payment grouping is forbidden if at least one payment is already valid. + """ + payment = Payment.objects.get(registrations=self.user.registration, final=False) + payment.valid = True + payment.save() + payment2 = Payment.objects.get(registrations=self.second_user.registration, final=False) + response = self.client.get(reverse('registration:update_payment_group_mode', args=(payment.pk,))) + self.assertEquals(response.status_code, 403) + + response = self.client.get(reverse('registration:update_payment_group_mode', args=(payment2.pk,))) + self.assertEquals(response.status_code, 403) + + def test_validate_payment(self): + """ + Try to validate a payment. + """ + payment = Payment.objects.get(registrations=self.user.registration, final=False) + payment.type = "other" + payment.valid = None + payment.save() + + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'valid': True}) + self.assertEquals(response.status_code, 403) + self.assertFalse(payment.valid) + + self.client.force_login(self.superuser) + + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'valid': True}) + self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200) + payment.refresh_from_db() + self.assertTrue(payment.valid) + + def test_invalidate_payment(self): + """ + Try to invalidate a payment. + """ + payment = Payment.objects.get(registrations=self.user.registration, final=False) + payment.type = "other" + payment.valid = None + payment.save() + + response = self.client.get(reverse('registration:update_payment', args=(payment.pk,))) + self.assertEquals(response.status_code, 200) + + self.client.force_login(self.superuser) + + response = self.client.post(reverse('registration:update_payment', args=(payment.pk,)), + data={'valid': False}) + self.assertRedirects(response, reverse('participation:team_detail', args=(self.team.pk,)), 302, 200) + payment.refresh_from_db() + self.assertFalse(payment.valid) + + class TestAdmin(TestCase): def setUp(self) -> None: self.user = User.objects.create_superuser( diff --git a/registration/apps.py b/registration/apps.py index 4bf3921..bb99e21 100644 --- a/registration/apps.py +++ b/registration/apps.py @@ -12,8 +12,8 @@ class RegistrationConfig(AppConfig): name = 'registration' def ready(self): - from registration.signals import create_admin_registration, \ - set_username, send_email_link - pre_save.connect(set_username, "auth.User") - pre_save.connect(send_email_link, "auth.User") - post_save.connect(create_admin_registration, "auth.User") + from registration import signals + pre_save.connect(signals.set_username, 'auth.User') + pre_save.connect(signals.send_email_link, 'auth.User') + pre_save.connect(signals.update_payment_amount, 'registration.Payment') + post_save.connect(signals.create_admin_registration, 'auth.User') diff --git a/registration/forms.py b/registration/forms.py index 02bb946..dc6c3d6 100644 --- a/registration/forms.py +++ b/registration/forms.py @@ -226,6 +226,9 @@ class PaymentAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["valid"].widget.choices[0] = ('unknown', _("Pending")) + payment_type = kwargs.get('data', {}).get('type', "") + self.fields['receipt'].required = payment_type in ["scholarship", "bank_transfer"] + self.fields['additional_information'].required = payment_type in ["other"] def clean_receipt(self): if "receipt" in self.files: diff --git a/registration/models.py b/registration/models.py index 3dad4a6..00b0603 100644 --- a/registration/models.py +++ b/registration/models.py @@ -387,7 +387,7 @@ class StudentRegistration(ParticipantRegistration): 'priority': 3, 'content': content, }) - elif self.payment.valid is None: + elif payment.valid is None: text = _("Your payment is under approval.") content = text informations.append({ diff --git a/registration/signals.py b/registration/signals.py index eda4107..55a05cd 100644 --- a/registration/signals.py +++ b/registration/signals.py @@ -41,3 +41,13 @@ def create_admin_registration(instance, **_): """ if instance.is_superuser: VolunteerRegistration.objects.get_or_create(user=instance, admin=True) + + +def update_payment_amount(instance, **_): + """ + When a payment got created, ensure that the amount is set. + """ + if instance.type == 'free' or instance.type == 'scholarship': + instance.amount = 0 + elif instance.pk: + instance.amount = instance.registrations.count() * instance.tournament.price