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

1from unittest.mock import MagicMock, PropertyMock, patch 

2 

3from django.conf import settings 

4from django.test import TestCase, override_settings 

5from django.utils import timezone 

6 

7from freezegun import freeze_time 

8 

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 

14 

15 

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.""" 

21 

22 fixtures = ["members.json"] 

23 

24 @classmethod 

25 def setUpTestData(cls): 

26 cls.member = PaymentUser.objects.filter(last_name="Wiggers").first() 

27 

28 def setUp(self): 

29 payables.register(MockModel, MockPayable) 

30 

31 def tearDown(self): 

32 payables._unregister(MockModel) 

33 

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 ) 

75 

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 ) 

83 

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 ) 

91 

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() 

97 

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 ) 

111 

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() 

119 

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) 

130 

131 existing_payment.created_at = timezone.now() 

132 

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) 

142 

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 ) 

164 

165 self.assertEqual(services.update_last_used(BankAccount.objects), 1) 

166 

167 self.assertEqual( 

168 BankAccount.objects.filter(mandate_no="11-2").first().last_used, 

169 timezone.now().date(), 

170 ) 

171 

172 self.assertEqual( 

173 services.update_last_used( 

174 BankAccount.objects, timezone.datetime(year=2018, month=12, day=12) 

175 ), 

176 1, 

177 ) 

178 

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 ) 

183 

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 ) 

205 

206 self.assertEqual(BankAccount.objects.filter(valid_until=None).count(), 2) 

207 

208 services.revoke_old_mandates() 

209 

210 self.assertEqual(BankAccount.objects.filter(valid_until=None).count(), 1) 

211 

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() 

229 

230 services.process_batch(b) 

231 

232 mock_mails.assert_called_once() 

233 ba.refresh_from_db() 

234 self.assertEqual(b.withdrawal_date, ba.last_used) 

235 

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) 

248 

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) 

260 

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)