Coverage for website/payments/tests/test_services.py: 100.00%
120 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1from unittest.mock import MagicMock, PropertyMock, patch
3from django.conf import settings
4from django.test import TestCase, override_settings
5from django.utils import timezone
7from freezegun import freeze_time
9from payments import services
10from payments.exceptions import PaymentError
11from payments.models import BankAccount, Batch, Payment, PaymentUser
12from payments.payables import payables
13from payments.tests.__mocks__ import MockModel, MockPayable
16@freeze_time("2019-01-01")
17@override_settings(SUSPEND_SIGNALS=True, THALIA_PAY_ENABLED_PAYMENT_METHOD=True)
18@patch("payments.models.PaymentUser.tpay_allowed", PropertyMock, True)
19class ServicesTest(TestCase):
20 """Test for the services."""
22 fixtures = ["members.json"]
24 @classmethod
25 def setUpTestData(cls):
26 cls.member = PaymentUser.objects.filter(last_name="Wiggers").first()
28 def setUp(self):
29 payables.register(MockModel, MockPayable)
31 def tearDown(self):
32 payables._unregister(MockModel)
34 def test_create_payment(self):
35 with self.subTest("Creates new payment with right payment type"):
36 p = services.create_payment(
37 MockPayable(MockModel(self.member)), self.member, Payment.CASH
38 )
39 self.assertEqual(p.amount, 5)
40 self.assertEqual(p.topic, "mock topic")
41 self.assertEqual(p.notes, "mock notes")
42 self.assertEqual(p.paid_by, self.member)
43 self.assertEqual(p.processed_by, self.member)
44 self.assertEqual(p.type, Payment.CASH)
45 with self.subTest("Updates payment if one already exists"):
46 existing_payment = Payment(amount=2)
47 p = services.create_payment(
48 MockPayable(MockModel(payer=self.member, payment=existing_payment)),
49 self.member,
50 Payment.CASH,
51 )
52 self.assertEqual(p, existing_payment)
53 self.assertEqual(p.amount, 5)
54 with self.subTest(
55 "Does not allow when user cannot manage payments for payable"
56 ):
57 with self.assertRaisesMessage(
58 PaymentError,
59 "User processing payment does not have the right permissions",
60 ):
61 payable = MockPayable(MockModel(payer=None, can_manage=False))
62 services.create_payment(payable, self.member, Payment.TPAY)
63 with self.subTest("Does not allow Thalia Pay when not enabled"):
64 with self.assertRaises(PaymentError):
65 services.create_payment(
66 MockPayable(MockModel(payer=self.member)), self.member, Payment.TPAY
67 )
68 with self.subTest("Do not allow zero euro payments"):
69 with self.assertRaises(PaymentError):
70 services.create_payment(
71 MockPayable(MockModel(payer=self.member, amount=0)),
72 self.member,
73 Payment.TPAY,
74 )
76 with self.subTest("Only allow valid payment types"):
77 with self.assertRaises(PaymentError):
78 services.create_payment(
79 MockPayable(MockModel(payer=self.member)),
80 self.member,
81 "no_payment",
82 )
84 with self.subTest("Do not allow creating payment when not paying_allowed"):
85 with self.assertRaises(PaymentError):
86 services.create_payment(
87 MockPayable(MockModel(payer=self.member, paying_allowed=False)),
88 self.member,
89 Payment.CASH,
90 )
92 def test_delete_payment(self):
93 existing_payment = MagicMock(batch=None)
94 payable = MockPayable(MockModel(payer=self.member, payment=existing_payment))
95 payable.model.save = MagicMock()
96 payable.model.save.reset_mock()
98 with self.subTest(
99 "Does not allow when user cannot manage payments for payable"
100 ):
101 with self.assertRaisesMessage(
102 PaymentError,
103 "User deleting payment does not have the right permissions.",
104 ):
105 services.delete_payment(
106 MockModel(
107 payer=self.member, payment=existing_payment, can_manage=False
108 ),
109 self.member,
110 )
112 with self.subTest("Within deletion window"):
113 payable.model.payment = existing_payment
114 existing_payment.created_at = timezone.now()
115 services.delete_payment(payable.model, self.member)
116 self.assertIsNone(payable.payment)
117 payable.model.save.assert_called_once()
118 existing_payment.delete.assert_called_once()
120 with self.subTest("Outside deletion window"):
121 payable.model.payment = existing_payment
122 existing_payment.created_at = timezone.now() - timezone.timedelta(
123 seconds=settings.PAYMENT_CHANGE_WINDOW + 60
124 )
125 with self.assertRaisesMessage(
126 PaymentError, "This payment cannot be deleted anymore."
127 ):
128 services.delete_payment(payable.model, self.member)
129 self.assertIsNotNone(payable.payment)
131 existing_payment.created_at = timezone.now()
133 with self.subTest("Already processed"):
134 payable.model.payment = existing_payment
135 existing_payment.batch = Batch.objects.create(processed=True)
136 with self.assertRaisesMessage(
137 PaymentError,
138 "This payment has already been processed and hence cannot be deleted.",
139 ):
140 services.delete_payment(payable.model)
141 self.assertIsNotNone(payable.payment)
143 def test_update_last_used(self):
144 BankAccount.objects.create(
145 owner=self.member,
146 initials="J",
147 last_name="Test",
148 iban="NL91ABNA0417164300",
149 mandate_no="11-1",
150 valid_from=timezone.now().date() - timezone.timedelta(days=2000),
151 valid_until=timezone.now().date() - timezone.timedelta(days=1500),
152 signature="base64,png",
153 )
154 BankAccount.objects.create(
155 owner=self.member,
156 initials="J",
157 last_name="Test",
158 iban="NL91ABNA0417164300",
159 mandate_no="11-2",
160 valid_from=timezone.now().date() - timezone.timedelta(days=5),
161 last_used=timezone.now().date() - timezone.timedelta(days=5),
162 signature="base64,png",
163 )
165 self.assertEqual(services.update_last_used(BankAccount.objects), 1)
167 self.assertEqual(
168 BankAccount.objects.filter(mandate_no="11-2").first().last_used,
169 timezone.now().date(),
170 )
172 self.assertEqual(
173 services.update_last_used(
174 BankAccount.objects, timezone.datetime(year=2018, month=12, day=12)
175 ),
176 1,
177 )
179 self.assertEqual(
180 BankAccount.objects.filter(mandate_no="11-2").first().last_used,
181 timezone.datetime(year=2018, month=12, day=12).date(),
182 )
184 def test_revoke_old_mandates(self):
185 BankAccount.objects.create(
186 owner=self.member,
187 initials="J",
188 last_name="Test1",
189 iban="NL91ABNA0417164300",
190 mandate_no="11-1",
191 valid_from=timezone.now().date() - timezone.timedelta(days=2000),
192 last_used=timezone.now().date() - timezone.timedelta(days=2000),
193 signature="base64,png",
194 )
195 BankAccount.objects.create(
196 owner=self.member,
197 initials="J",
198 last_name="Test2",
199 iban="NL91ABNA0417164300",
200 mandate_no="11-2",
201 valid_from=timezone.now().date() - timezone.timedelta(days=5),
202 last_used=timezone.now().date() - timezone.timedelta(days=5),
203 signature="base64,png",
204 )
206 self.assertEqual(BankAccount.objects.filter(valid_until=None).count(), 2)
208 services.revoke_old_mandates()
210 self.assertEqual(BankAccount.objects.filter(valid_until=None).count(), 1)
212 def test_process_batch(self):
213 with patch("payments.services.send_tpay_batch_processing_emails") as mock_mails:
214 ba = BankAccount.objects.create(
215 owner=self.member,
216 initials="J",
217 last_name="Test1",
218 iban="NL91ABNA0417164300",
219 mandate_no="11-1",
220 valid_from=timezone.now().date() - timezone.timedelta(days=2000),
221 signature="base64,png",
222 )
223 p = services.create_payment(
224 MockPayable(MockModel(self.member)), self.member, Payment.TPAY
225 )
226 b = Batch.objects.create()
227 p.batch = b
228 p.save()
230 services.process_batch(b)
232 mock_mails.assert_called_once()
233 ba.refresh_from_db()
234 self.assertEqual(b.withdrawal_date, ba.last_used)
236 def test_data_minimisation(self):
237 with self.subTest("Payments that are 7 years old must be minimised"):
238 p = Payment.objects.create(
239 paid_by=self.member,
240 amount=1,
241 created_at=timezone.now() - timezone.timedelta(days=(365 * 7) + 1),
242 topic="test",
243 notes="to be deleted",
244 )
245 services.execute_data_minimisation(dry_run=False)
246 payment = Payment.objects.get(pk=p.pk)
247 self.assertIsNone(payment.paid_by)
249 with self.subTest("Payments that are not 7 years old must not be minimised"):
250 p = Payment.objects.create(
251 paid_by=self.member,
252 amount=1,
253 created_at=timezone.now() - timezone.timedelta(days=(365 * 7) - 1),
254 topic="test",
255 notes="to be deleted",
256 )
257 services.execute_data_minimisation(dry_run=False)
258 payment = Payment.objects.get(pk=p.pk)
259 self.assertIsNotNone(payment.paid_by)
261 with self.subTest("Dry run should not actually delete"):
262 p = Payment.objects.create(
263 paid_by=self.member,
264 amount=1,
265 created_at=timezone.now() - timezone.timedelta(days=(365 * 7) + 1),
266 topic="test",
267 notes="to be deleted",
268 )
269 services.execute_data_minimisation(dry_run=True)
270 payment = Payment.objects.get(pk=p.pk)
271 self.assertIsNotNone(payment.paid_by)