Coverage for website/payments/tests/test_admin.py: 99.41%

485 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2025-08-14 10:31 +0000

1import datetime 

2from decimal import Decimal 

3from unittest import mock 

4from unittest.mock import MagicMock, Mock, PropertyMock, patch 

5 

6from django.contrib import messages 

7from django.contrib.admin import AdminSite 

8from django.contrib.admin.utils import model_ngettext 

9from django.contrib.auth.models import Permission 

10from django.contrib.contenttypes.models import ContentType 

11from django.core.exceptions import ObjectDoesNotExist, ValidationError 

12from django.http import HttpRequest 

13from django.test import ( 

14 Client, 

15 RequestFactory, 

16 SimpleTestCase, 

17 TestCase, 

18 override_settings, 

19) 

20from django.urls import reverse 

21from django.utils import timezone 

22from django.utils.translation import gettext_lazy as _ 

23 

24from freezegun import freeze_time 

25 

26from events.models import Event, EventRegistration 

27from members.models import Member, Profile 

28from payments import admin, services 

29from payments.admin import BankAccountInline, PaymentInline, ValidAccountFilter 

30from payments.forms import BatchPaymentInlineAdminForm 

31from payments.models import BankAccount, Batch, Payment, PaymentUser 

32 

33 

34class GlobalAdminTest(SimpleTestCase): 

35 @mock.patch("registrations.admin.RegistrationAdmin") 

36 def test_show_message(self, admin_mock) -> None: 

37 admin_mock.return_value = admin_mock 

38 request = Mock(spec=HttpRequest) 

39 

40 admin._show_message(admin_mock, request, 0, "message", "error") 

41 admin_mock.message_user.assert_called_once_with( 

42 request, "error", messages.ERROR 

43 ) 

44 admin_mock.message_user.reset_mock() 

45 admin._show_message(admin_mock, request, 1, "message", "error") 

46 admin_mock.message_user.assert_called_once_with( 

47 request, "message", messages.SUCCESS 

48 ) 

49 

50 

51@override_settings(SUSPEND_SIGNALS=True, THALIA_PAY_ENABLED_PAYMENT_METHOD=True) 

52@patch("payments.models.PaymentUser.tpay_allowed", PropertyMock, True) 

53class PaymentAdminTest(TestCase): 

54 fixtures = ["members.json", "bank_accounts.json"] 

55 

56 @classmethod 

57 def setUpTestData(cls) -> None: 

58 cls.user = PaymentUser.objects.get(pk=2) 

59 

60 def setUp(self) -> None: 

61 self.client = Client() 

62 self.client.force_login(self.user) 

63 self.factory = RequestFactory() 

64 self.site = AdminSite() 

65 self.admin = admin.PaymentAdmin(Payment, admin_site=self.site) 

66 

67 self._give_user_permissions() 

68 process_perm_batch = Permission.objects.get( 

69 content_type__model="batch", codename="process_batches" 

70 ) 

71 self.user.user_permissions.remove(process_perm_batch) 

72 self.client.logout() 

73 self.client.force_login(self.user) 

74 

75 def _give_user_permissions(self, batch_permissions=True) -> None: 

76 """Helper to give the user permissions.""" 

77 content_type = ContentType.objects.get_for_model(Payment) 

78 permissions_p = content_type.permission_set.all() 

79 content_type = ContentType.objects.get_for_model(Batch) 

80 permissions_b = content_type.permission_set.all() 

81 for p in permissions_p: 

82 self.user.user_permissions.add(p) 

83 if batch_permissions: 

84 for p in permissions_b: 

85 self.user.user_permissions.add(p) 

86 

87 self.user.save() 

88 

89 self.client.logout() 

90 self.client.force_login(self.user) 

91 

92 def test_paid_by_link(self) -> None: 

93 """Tests that the right link for the paying user is returned.""" 

94 payment = Payment.objects.create( 

95 amount=7.5, paid_by=self.user, processed_by=self.user, type=Payment.CASH 

96 ) 

97 

98 self.assertEqual( 

99 self.admin.paid_by_link(payment), 

100 f"<a href='/members/profile/{self.user.pk}'>Sébastiaan Versteeg</a>", 

101 ) 

102 

103 def test_processed_by_link(self) -> None: 

104 """Tests that the right link for the processing user is returned.""" 

105 payment1 = Payment.objects.create( 

106 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.CASH 

107 ) 

108 

109 self.assertEqual( 

110 self.admin.processed_by_link(payment1), 

111 f"<a href='/members/profile/{self.user.pk}'>Sébastiaan Versteeg</a>", 

112 ) 

113 

114 def test_delete_model_succeed(self) -> None: 

115 batch = Batch.objects.create() 

116 payment = Payment.objects.create( 

117 amount=1, 

118 paid_by=self.user, 

119 processed_by=self.user, 

120 type=Payment.TPAY, 

121 batch=batch, 

122 ) 

123 self.client.post( 

124 reverse("admin:payments_payment_delete", args=(payment.id,)), 

125 {"post": "yes"}, # Add data to confirm deletion in admin 

126 ) 

127 self.assertFalse(Payment.objects.filter(id=payment.id).exists()) 

128 

129 def test_delete_model_fail(self) -> None: 

130 batch = Batch.objects.create() 

131 payment = Payment.objects.create( 

132 amount=1, 

133 paid_by=self.user, 

134 processed_by=self.user, 

135 type=Payment.TPAY, 

136 batch=batch, 

137 ) 

138 batch.processed = True 

139 batch.save() 

140 response = self.client.post( 

141 reverse("admin:payments_payment_delete", args=(payment.id,)), 

142 {"post": "yes"}, # Add data to confirm deletion in admin 

143 ) 

144 self.assertEqual(response.status_code, 403) 

145 self.assertTrue(Payment.objects.filter(id=payment.id).exists()) 

146 

147 def test_delete_action_fail(self) -> None: 

148 batch = Batch.objects.create() 

149 batch_proc = Batch.objects.create() 

150 Payment.objects.create( 

151 amount=1, 

152 paid_by=self.user, 

153 processed_by=self.user, 

154 type=Payment.TPAY, 

155 batch=batch, 

156 ) 

157 payment2 = Payment.objects.create( 

158 amount=1, 

159 paid_by=self.user, 

160 processed_by=self.user, 

161 type=Payment.TPAY, 

162 batch=batch_proc, 

163 ) 

164 batch_proc.processed = True 

165 batch_proc.save() 

166 self.client.post( 

167 reverse("admin:payments_payment_changelist"), 

168 { 

169 "action": "delete_selected", 

170 "_selected_action": Payment.objects.values_list("id", flat=True), 

171 "post": "yes", 

172 }, 

173 ) 

174 self.assertTrue(Payment.objects.filter(id=payment2.id).exists()) 

175 

176 def test_delete_action_success(self) -> None: 

177 batch = Batch.objects.create() 

178 payment1 = Payment.objects.create( 

179 amount=1, 

180 processed_by=self.user, 

181 paid_by=self.user, 

182 type=Payment.TPAY, 

183 batch=batch, 

184 ) 

185 payment2 = Payment.objects.create( 

186 amount=1, 

187 processed_by=self.user, 

188 paid_by=self.user, 

189 type=Payment.TPAY, 

190 batch=batch, 

191 ) 

192 self.client.post( 

193 reverse("admin:payments_payment_changelist"), 

194 { 

195 "action": "delete_selected", 

196 "_selected_action": Payment.objects.values_list("id", flat=True), 

197 "post": "yes", 

198 }, 

199 ) 

200 self.assertFalse( 

201 Payment.objects.filter(id__in=[payment1.id, payment2.id]).exists() 

202 ) 

203 

204 def test_has_delete_permission_get(self) -> None: 

205 payment = Payment.objects.create( 

206 amount=10, paid_by=self.user, processed_by=self.user, type=Payment.CASH 

207 ) 

208 request = self.factory.get( 

209 reverse("admin:payments_payment_delete", args=(payment.id,)) 

210 ) 

211 request.user = self.user 

212 self.admin.has_delete_permission(request) 

213 self.assertTrue(Payment.objects.filter(id=payment.id).exists()) 

214 

215 @freeze_time("2020-01-01") 

216 def test_batch_link(self) -> None: 

217 batch = Batch.objects.create(id=1) 

218 payment1 = Payment.objects.create( 

219 amount=7.5, 

220 processed_by=self.user, 

221 paid_by=self.user, 

222 type=Payment.TPAY, 

223 batch=batch, 

224 ) 

225 payment2 = Payment.objects.create( 

226 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.TPAY 

227 ) 

228 payment3 = Payment.objects.create( 

229 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.WIRE 

230 ) 

231 self.assertEqual( 

232 "<a href='/admin/payments/batch/1/change/'>Thalia Pay payments for 2020-1 (not processed)</a>", 

233 str(self.admin.batch_link(payment1)), 

234 ) 

235 self.assertEqual("No batch attached", self.admin.batch_link(payment2)) 

236 self.assertEqual("", self.admin.batch_link(payment3)) 

237 

238 def test_add_to_new_batch(self) -> None: 

239 p1 = Payment.objects.create( 

240 amount=1, processed_by=self.user, paid_by=self.user, type=Payment.CARD 

241 ) 

242 p2 = Payment.objects.create( 

243 amount=2, processed_by=self.user, paid_by=self.user, type=Payment.CASH 

244 ) 

245 p3 = Payment.objects.create( 

246 amount=3, processed_by=self.user, paid_by=self.user, type=Payment.TPAY 

247 ) 

248 

249 change_url = reverse("admin:payments_payment_changelist") 

250 

251 self._give_user_permissions(batch_permissions=False) 

252 self.client.post( 

253 change_url, 

254 { 

255 "action": "add_to_new_batch", 

256 "index": 1, 

257 "_selected_action": [x.id for x in [p1, p2, p3]], 

258 }, 

259 ) 

260 

261 for p in Payment.objects.all(): 

262 self.assertIsNone(p.batch) 

263 

264 self._give_user_permissions() 

265 self.client.post( 

266 change_url, 

267 { 

268 "action": "add_to_new_batch", 

269 "index": 1, 

270 "_selected_action": [x.id for x in [p1, p2, p3]], 

271 }, 

272 ) 

273 

274 for p in Payment.objects.filter(id__in=[p1.id, p2.id]): 

275 self.assertIsNone(p.batch) 

276 

277 self.assertIsNotNone(Payment.objects.get(id=p3.id).batch.id) 

278 

279 self._give_user_permissions() 

280 self.client.post( 

281 change_url, 

282 { 

283 "action": "add_to_new_batch", 

284 "index": 1, 

285 "_selected_action": [x.id for x in [p1, p2]], 

286 }, 

287 ) 

288 

289 def test_add_to_last_batch(self) -> None: 

290 b = Batch.objects.create() 

291 p1 = Payment.objects.create( 

292 amount=1, processed_by=self.user, paid_by=self.user, type=Payment.CARD 

293 ) 

294 p2 = Payment.objects.create( 

295 amount=2, processed_by=self.user, paid_by=self.user, type=Payment.CASH 

296 ) 

297 p3 = Payment.objects.create( 

298 amount=3, processed_by=self.user, paid_by=self.user, type=Payment.TPAY 

299 ) 

300 

301 change_url = reverse("admin:payments_payment_changelist") 

302 

303 self._give_user_permissions(batch_permissions=False) 

304 self.client.post( 

305 change_url, 

306 { 

307 "action": "add_to_last_batch", 

308 "index": 1, 

309 "_selected_action": [x.id for x in [p1, p2, p3]], 

310 }, 

311 ) 

312 

313 for p in Payment.objects.all(): 

314 self.assertIsNone(p.batch) 

315 

316 self._give_user_permissions() 

317 self.client.post( 

318 change_url, 

319 { 

320 "action": "add_to_last_batch", 

321 "index": 1, 

322 "_selected_action": [x.id for x in [p1, p2, p3]], 

323 }, 

324 ) 

325 

326 for p in Payment.objects.filter(id__in=[p1.id, p2.id]): 

327 self.assertIsNone(p.batch) 

328 

329 self.assertEqual(Payment.objects.get(id=p3.id).batch.id, b.id) 

330 

331 self.client.post( 

332 change_url, 

333 { 

334 "action": "add_to_last_batch", 

335 "index": 1, 

336 "_selected_action": [x.id for x in [p1, p2]], 

337 }, 

338 ) 

339 

340 b.processed = True 

341 b.save() 

342 

343 self.client.post( 

344 change_url, 

345 { 

346 "action": "add_to_last_batch", 

347 "index": 1, 

348 "_selected_action": [p3.id], 

349 }, 

350 ) 

351 

352 def test_add_to_last_batch_no_batch(self): 

353 p3 = Payment.objects.create( 

354 amount=3, processed_by=self.user, paid_by=self.user, type=Payment.TPAY 

355 ) 

356 

357 change_url = reverse("admin:payments_payment_changelist") 

358 

359 self._give_user_permissions() 

360 

361 try: 

362 self.client.post( 

363 change_url, 

364 { 

365 "action": "add_to_last_batch", 

366 "index": 1, 

367 "_selected_action": [p3.id], 

368 }, 

369 ) 

370 except ObjectDoesNotExist: 

371 self.fail("Add to last batch should work without a batch") 

372 

373 def test_get_actions(self) -> None: 

374 """Test that the actions are added to the admin.""" 

375 response = self.client.get(reverse("admin:payments_payment_changelist")) 

376 

377 actions = self.admin.get_actions(response.wsgi_request) 

378 self.assertCountEqual(actions, ["delete_selected", "export_csv"]) 

379 

380 self._give_user_permissions() 

381 response = self.client.get(reverse("admin:payments_payment_changelist")) 

382 

383 actions = self.admin.get_actions(response.wsgi_request) 

384 self.assertCountEqual( 

385 actions, 

386 [ 

387 "delete_selected", 

388 "add_to_new_batch", 

389 "add_to_last_batch", 

390 "export_csv", 

391 ], 

392 ) 

393 

394 def test_get_readonly_fields(self) -> None: 

395 """Test that the custom urls are added to the admin.""" 

396 with self.subTest("No object"): 

397 urls = self.admin.get_readonly_fields(HttpRequest(), None) 

398 self.assertEqual( 

399 urls, ("created_at", "processed_by", "payable_object", "batch") 

400 ) 

401 

402 with self.subTest("With object"): 

403 urls = self.admin.get_readonly_fields(HttpRequest(), Payment()) 

404 self.assertEqual( 

405 urls, 

406 ( 

407 "created_at", 

408 "processed_by", 

409 "payable_object", 

410 "amount", 

411 "paid_by", 

412 "type", 

413 "topic", 

414 "notes", 

415 "batch", 

416 ), 

417 ) 

418 

419 def test_get_urls(self) -> None: 

420 """Test that the custom urls are added to the admin.""" 

421 urls = self.admin.get_urls() 

422 self.assertEqual(urls[0].name, "payments_payment_create") 

423 

424 @freeze_time("2019-01-01") 

425 def test_export_csv(self) -> None: 

426 """Test that the CSV export of payments is correct.""" 

427 Payment.objects.create( 

428 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.CARD 

429 ) 

430 Payment.objects.create( 

431 amount=17.5, processed_by=self.user, paid_by=self.user, type=Payment.CASH 

432 ) 

433 

434 response = self.admin.export_csv(HttpRequest(), Payment.objects.all()) 

435 

436 self.assertEqual( 

437 f"Created,Amount,Type,Processor,Payer id,Payer name," 

438 f"Notes\r\n2019-01-01 00:00:00+00:00," 

439 f"7.50,Card payment,Sébastiaan Versteeg,{self.user.pk},Sébastiaan Versteeg," 

440 f"\r\n2019-01-01 00:00:00+00:00,17.50," 

441 f"Cash payment,Sébastiaan Versteeg,{self.user.pk},Sébastiaan Versteeg," 

442 f"\r\n", 

443 response.content.decode("utf-8"), 

444 ) 

445 

446 def test_get_field_queryset(self) -> None: 

447 b1 = Batch.objects.create(id=1) 

448 Batch.objects.create(id=2, processed=True) 

449 p1 = Payment.objects.create( 

450 amount=5, paid_by=self.user, processed_by=self.user, type=Payment.TPAY 

451 ) 

452 response = self.client.get( 

453 reverse("admin:payments_payment_change", args=(p1.id,)) 

454 ) 

455 self.assertCountEqual( 

456 [ 

457 int(x.id) 

458 for x in response.context_data["adminform"] 

459 .form.fields["batch"] 

460 .choices.queryset 

461 ], 

462 [b1.id], 

463 ) 

464 

465 self.client.get(reverse("admin:payments_payment_add")) 

466 

467 def test_payable_model_filter(self): 

468 # Payment with no payable object. 

469 p1 = Payment.objects.create( 

470 amount=7.5, processed_by=self.user, paid_by=self.user, type=Payment.CARD 

471 ) 

472 

473 event = Event.objects.create( 

474 title="testevent", 

475 description="desc", 

476 start=timezone.now(), 

477 end=(timezone.now() + datetime.timedelta(hours=1)), 

478 location="test location", 

479 map_location="test map location", 

480 price=1.00, 

481 fine=0.00, 

482 ) 

483 

484 self.user.is_superuser = True 

485 self.user.save() 

486 

487 registration = EventRegistration.objects.create(event=event, member=self.user) 

488 services.create_payment(registration, self.user, Payment.WIRE) 

489 registration.refresh_from_db() 

490 p2 = registration.payment 

491 

492 with self.subTest("EventRegistration"): 

493 response = self.client.get( 

494 reverse("admin:payments_payment_changelist"), 

495 {"payable_model": "events_registration"}, 

496 ) 

497 self.assertNotContains(response, f"/admin/payments/payment/{p1.id}/change/") 

498 self.assertContains(response, f"/admin/payments/payment/{p2.id}/change/") 

499 

500 with self.subTest("None"): 

501 response = self.client.get( 

502 reverse("admin:payments_payment_changelist"), 

503 {"payable_model": "none"}, 

504 ) 

505 self.assertContains(response, f"/admin/payments/payment/{p1.id}/change/") 

506 self.assertNotContains(response, f"/admin/payments/payment/{p2.id}/change/") 

507 

508 with self.subTest("FoodOrder"): 

509 response = self.client.get( 

510 reverse("admin:payments_payment_changelist"), 

511 {"payable_model": "food_order"}, 

512 ) 

513 self.assertNotContains(response, f"/admin/payments/payment/{p1.id}/change/") 

514 self.assertNotContains(response, f"/admin/payments/payment/{p2.id}/change/") 

515 

516 with self.subTest("Invalid"): 

517 response = self.client.get( 

518 reverse("admin:payments_payment_changelist"), 

519 {"payable_model": "foo_bar_baz"}, 

520 follow=1, 

521 ) 

522 self.assertContains(response, f"/admin/payments/payment/{p1.id}/change/") 

523 self.assertContains(response, f"/admin/payments/payment/{p2.id}/change/") 

524 

525 with self.subTest("Changeview payable object"): 

526 response = self.client.get( 

527 reverse("admin:payments_payment_change", args=(p2.id,)), 

528 ) 

529 self.assertContains( 

530 response, f"/admin/events/eventregistration/{registration.id}/change/" 

531 ) 

532 

533 

534@freeze_time("2019-01-01") 

535@override_settings(SUSPEND_SIGNALS=True) 

536class ValidAccountFilterTest(TestCase): 

537 @classmethod 

538 def setUpTestData(cls) -> None: 

539 cls.member = PaymentUser.objects.create( 

540 username="test1", 

541 first_name="Test1", 

542 last_name="Example", 

543 email="test1@example.org", 

544 is_staff=True, 

545 ) 

546 Profile.objects.create(user=cls.member) 

547 

548 cls.member = PaymentUser.objects.get(pk=cls.member.pk) 

549 

550 cls.no_mandate = BankAccount.objects.create( 

551 owner=cls.member, initials="J", last_name="Test", iban="NL91ABNA0417164300" 

552 ) 

553 cls.valid_mandate = BankAccount.objects.create( 

554 owner=cls.member, 

555 initials="J", 

556 last_name="Test", 

557 iban="NL91ABNA0417164300", 

558 mandate_no="11-1", 

559 valid_from=timezone.now().date() - timezone.timedelta(days=5), 

560 signature="base64,png", 

561 ) 

562 cls.invalid_mandate = BankAccount.objects.create( 

563 owner=cls.member, 

564 initials="J", 

565 last_name="Test", 

566 iban="NL91ABNA0417164300", 

567 mandate_no="11-2", 

568 valid_from=timezone.now().date() - timezone.timedelta(days=5), 

569 valid_until=timezone.now().date(), 

570 signature="base64,png", 

571 ) 

572 

573 def setUp(self) -> None: 

574 self.site = AdminSite() 

575 self.admin = admin.BankAccountAdmin(BankAccount, admin_site=self.site) 

576 

577 def test_lookups(self) -> None: 

578 """Tests that the right options are implemented for lookups.""" 

579 account_filter = ValidAccountFilter( 

580 model=BankAccount, model_admin=self.admin, params={}, request=None 

581 ) 

582 

583 self.assertEqual( 

584 ( 

585 ("valid", "Valid"), 

586 ("invalid", "Invalid"), 

587 ("none", "None"), 

588 ), 

589 account_filter.lookups(None, None), 

590 ) 

591 

592 def test_queryset(self) -> None: 

593 """Tests that the right results are returned.""" 

594 for param, item in [ 

595 ("valid", self.valid_mandate), 

596 ("invalid", self.invalid_mandate), 

597 ("none", self.no_mandate), 

598 ]: 

599 with self.subTest(f"Status {param}"): 

600 account_filter = ValidAccountFilter( 

601 model=BankAccount, 

602 model_admin=self.admin, 

603 params={"active": [param]}, 

604 request=None, 

605 ) 

606 

607 result = account_filter.queryset(None, BankAccount.objects.all()) 

608 

609 self.assertEqual(result.count(), 1) 

610 self.assertEqual(result.first().pk, item.pk) 

611 

612 with self.subTest("No known param"): 

613 account_filter = ValidAccountFilter( 

614 model=BankAccount, 

615 model_admin=self.admin, 

616 params={"active": ["bla"]}, 

617 request=None, 

618 ) 

619 

620 result = account_filter.queryset(None, BankAccount.objects.all()) 

621 

622 self.assertEqual(result.count(), 3) 

623 

624 

625@freeze_time("2019-01-01") 

626@override_settings(SUSPEND_SIGNALS=True, THALIA_PAY_ENABLED_PAYMENT_METHOD=True) 

627@patch("payments.models.PaymentUser.tpay_allowed", PropertyMock, True) 

628class BatchAdminTest(TestCase): 

629 fixtures = ["members.json", "bank_accounts.json"] 

630 

631 @classmethod 

632 def setUpTestData(cls) -> None: 

633 cls.user = PaymentUser.objects.get(pk=2) 

634 

635 def setUp(self) -> None: 

636 self.client = Client() 

637 self.client.force_login(self.user) 

638 self.site = AdminSite() 

639 self.admin = admin.BatchAdmin(Batch, admin_site=self.site) 

640 self.rf = RequestFactory() 

641 

642 self.user.refresh_from_db() 

643 

644 self._give_user_permissions() 

645 process_perm_batch = Permission.objects.get( 

646 content_type__model="batch", codename="process_batches" 

647 ) 

648 self.user.user_permissions.remove(process_perm_batch) 

649 self.client.logout() 

650 self.client.force_login(self.user) 

651 

652 def _give_user_permissions(self, batch_permissions=True) -> None: 

653 """Helper to give the user permissions.""" 

654 content_type = ContentType.objects.get_for_model(Payment) 

655 permissions_p = content_type.permission_set.all() 

656 content_type = ContentType.objects.get_for_model(Batch) 

657 permissions_b = content_type.permission_set.all() 

658 for p in permissions_p: 

659 self.user.user_permissions.add(p) 

660 if batch_permissions: 660 ↛ 664line 660 didn't jump to line 664 because the condition on line 660 was always true

661 for p in permissions_b: 

662 self.user.user_permissions.add(p) 

663 

664 self.user.save() 

665 

666 self.client.logout() 

667 self.client.force_login(self.user) 

668 

669 def test_delete_model_succeed(self) -> None: 

670 batch = Batch.objects.create() 

671 self.client.post( 

672 reverse("admin:payments_batch_delete", args=(batch.id,)), 

673 {"post": "yes"}, # Add data to confirm deletion in admin 

674 ) 

675 self.assertFalse(Batch.objects.filter(id=batch.id).exists()) 

676 

677 def test_delete_model_fail(self) -> None: 

678 batch = Batch.objects.create(processed=True) 

679 response = self.client.post( 

680 reverse("admin:payments_batch_delete", args=(batch.id,)), 

681 {"post": "yes"}, 

682 ) 

683 self.assertEqual(response.status_code, 403) 

684 self.assertTrue(Batch.objects.filter(id=batch.id).exists()) 

685 

686 def test_delete_action_fail(self) -> None: 

687 Batch.objects.create() 

688 batch_proc = Batch.objects.create(processed=True) 

689 self.client.post( 

690 reverse("admin:payments_batch_changelist"), 

691 { 

692 "action": "delete_selected", 

693 "_selected_action": Batch.objects.values_list("id", flat=True), 

694 "post": "yes", 

695 }, 

696 ) 

697 self.assertTrue(Batch.objects.filter(id=batch_proc.id).exists()) 

698 

699 def test_delete_action_success(self) -> None: 

700 batch = Batch.objects.create() 

701 batch_proc = Batch.objects.create() 

702 self.client.post( 

703 reverse("admin:payments_batch_changelist"), 

704 { 

705 "action": "delete_selected", 

706 "_selected_action": Batch.objects.values_list("id", flat=True), 

707 "post": "yes", 

708 }, 

709 ) 

710 self.assertFalse( 

711 Batch.objects.filter(id__in=[batch_proc.id, batch.id]).exists() 

712 ) 

713 

714 def test_has_delete_permission_get(self) -> None: 

715 batch = Batch.objects.create() 

716 request = self.rf.get(reverse("admin:payments_batch_delete", args=(batch.id,))) 

717 request.user = self.user 

718 self.admin.has_delete_permission(request) 

719 self.assertTrue(Batch.objects.filter(id=batch.id).exists()) 

720 

721 def test_get_readonly_fields(self) -> None: 

722 b = Batch.objects.create() 

723 self.assertCountEqual( 

724 self.admin.get_readonly_fields(None, b), 

725 [ 

726 "id", 

727 "processed", 

728 "processing_date", 

729 "total_amount", 

730 ], 

731 ) 

732 

733 b.processed = True 

734 b.save() 

735 self.assertCountEqual( 

736 self.admin.get_readonly_fields(None, b), 

737 [ 

738 "id", 

739 "description", 

740 "processed", 

741 "processing_date", 

742 "total_amount", 

743 "withdrawal_date", 

744 ], 

745 ) 

746 

747 def test_save_formset(self) -> None: 

748 batch = Batch.objects.create() 

749 batch_processed = Batch.objects.create() 

750 Payment.objects.create( 

751 amount=1, 

752 paid_by=self.user, 

753 processed_by=self.user, 

754 type=Payment.TPAY, 

755 batch=batch, 

756 ) 

757 Payment.objects.create( 

758 amount=1, 

759 paid_by=self.user, 

760 processed_by=self.user, 

761 type=Payment.TPAY, 

762 batch=batch_processed, 

763 ) 

764 batch_processed.processed = True 

765 batch_processed.save() 

766 

767 formset = BatchPaymentInlineAdminForm() 

768 formset.save = MagicMock(return_value=Payment.objects.all()) 

769 formset.save_m2m = MagicMock() 

770 

771 with self.assertRaises(ValidationError): 

772 self.admin.save_formset(None, None, formset, None) 

773 

774 batch_processed.processed = False 

775 batch_processed.save() 

776 

777 formset.save.return_value = Payment.objects.all() 

778 self.admin.save_formset(None, None, formset, None) 

779 

780 @mock.patch("django.contrib.admin.ModelAdmin.changeform_view") 

781 def test_change_form_view(self, changeform_view_mock) -> None: 

782 self._give_user_permissions() 

783 

784 b = Batch.objects.create(processed=True) 

785 request = self.rf.get(f"/admin/payments/batch/{b.id}/change/") 

786 request.user = self.user 

787 self.admin.changeform_view(request, b.id) 

788 

789 changeform_view_mock.assert_called_once_with(request, b.id, "", {"batch": b}) 

790 

791 changeform_view_mock.reset_mock() 

792 

793 self.admin.changeform_view(request) 

794 

795 changeform_view_mock.assert_called_once_with(request, None, "", {"batch": None}) 

796 

797 

798@freeze_time("2019-01-01") 

799@override_settings(SUSPEND_SIGNALS=True) 

800class BankAccountAdminTest(TestCase): 

801 @classmethod 

802 def setUpTestData(cls) -> None: 

803 cls.user = Member.objects.create( 

804 username="test1", 

805 first_name="Test1", 

806 last_name="Example", 

807 email="test1@example.org", 

808 is_staff=True, 

809 is_superuser=True, 

810 ) 

811 Profile.objects.create(user=cls.user) 

812 

813 cls.user = PaymentUser.objects.get(pk=cls.user.pk) 

814 

815 def setUp(self) -> None: 

816 self.site = AdminSite() 

817 self.admin = admin.BankAccountAdmin(BankAccount, admin_site=self.site) 

818 self.rf = RequestFactory() 

819 

820 def test_owner_link(self) -> None: 

821 """Test that the link to a member profile is correct.""" 

822 bank_account1 = BankAccount.objects.create( 

823 owner=self.user, initials="J", last_name="Test", iban="NL91ABNA0417164300" 

824 ) 

825 

826 self.assertEqual( 

827 self.admin.owner_link(bank_account1), 

828 f"<a href='/admin/auth/user/{self.user.pk}/change/'>Test1 Example</a>", 

829 ) 

830 

831 bank_account2 = BankAccount.objects.create( 

832 owner=None, initials="J2", last_name="Test", iban="NL91ABNA0417164300" 

833 ) 

834 

835 self.assertEqual(self.admin.owner_link(bank_account2), "") 

836 

837 def test_can_be_revoked(self) -> None: 

838 """Test that the revocation value of a bank account is correct.""" 

839 bank_account1 = BankAccount.objects.create( 

840 owner=self.user, initials="J", last_name="Test", iban="NL91ABNA0417164300" 

841 ) 

842 

843 with patch( 

844 "payments.models.BankAccount.can_be_revoked", new_callable=PropertyMock 

845 ) as mock: 

846 mock.return_value = True 

847 self.assertTrue(self.admin.can_be_revoked(bank_account1)) 

848 mock.return_value = False 

849 self.assertFalse( 

850 self.admin.can_be_revoked(bank_account1), 

851 ) 

852 

853 def test_export_csv(self) -> None: 

854 """Test that the CSV export of accounts is correct.""" 

855 BankAccount.objects.create( 

856 owner=self.user, initials="J", last_name="Test", iban="NL91ABNA0417164300" 

857 ) 

858 BankAccount.objects.create( 

859 owner=self.user, initials="J2", last_name="Test", iban="NL91ABNA0417164300" 

860 ) 

861 BankAccount.objects.create( 

862 owner=self.user, 

863 initials="J3", 

864 last_name="Test", 

865 iban="NL91ABNA0417164300", 

866 mandate_no="12-1", 

867 valid_from=timezone.now().date() - timezone.timedelta(days=5), 

868 valid_until=timezone.now().date(), 

869 signature="sig", 

870 ) 

871 BankAccount.objects.create( 

872 owner=self.user, 

873 initials="J4", 

874 last_name="Test", 

875 iban="DE12500105170648489890", 

876 bic="NBBEBEBB", 

877 mandate_no="11-1", 

878 valid_from=timezone.now().date(), 

879 valid_until=timezone.now().date() + timezone.timedelta(days=5), 

880 signature="sig", 

881 ) 

882 

883 response = self.admin.export_csv(HttpRequest(), BankAccount.objects.all()) 

884 

885 self.assertEqual( 

886 b"Created,Name,Reference,IBAN,BIC,Valid from,Valid until," 

887 b"Signature\r\n2019-01-01 00:00:00+00:00,J Test,," 

888 b"NL91ABNA0417164300,,,,\r\n2019-01-01 00:00:00+00:00,J2 Test,," 

889 b"NL91ABNA0417164300,,,,\r\n2019-01-01 00:00:00+00:00,J3 Test," 

890 b"12-1,NL91ABNA0417164300,,2018-12-27,2019-01-01," 

891 b"sig\r\n2019-01-01 00:00:00+00:00,J4 Test,11-1," 

892 b"DE12500105170648489890,NBBEBEBB,2019-01-01,2019-01-06,sig\r\n", 

893 response.content, 

894 ) 

895 

896 @mock.patch("django.contrib.admin.ModelAdmin.message_user") 

897 @mock.patch("payments.services.update_last_used") 

898 def test_set_last_used(self, update_last_used, message_user) -> None: 

899 """Tests that the last used value is updated.""" 

900 update_last_used.return_value = 1 

901 

902 request_noperms = self.rf.post( 

903 "/", {"action": "set_last_used", "index": 1, "_selected_action": ["bla"]} 

904 ) 

905 request_noperms.user = MagicMock() 

906 request_noperms.user.has_perm = lambda _: False 

907 

908 request_hasperms = self.rf.post( 

909 "/", {"action": "set_last_used", "index": 1, "_selected_action": ["bla"]} 

910 ) 

911 request_hasperms.user = MagicMock() 

912 request_hasperms.user.has_perm = lambda _: True 

913 

914 update_last_used.reset_mock() 

915 message_user.reset_mock() 

916 

917 queryset_mock = MagicMock() 

918 

919 self.admin.set_last_used(request_noperms, queryset_mock) 

920 update_last_used.assert_not_called() 

921 

922 self.admin.set_last_used(request_hasperms, queryset_mock) 

923 update_last_used.assert_called_once_with(queryset_mock) 

924 

925 message_user.assert_called_once_with( 

926 request_hasperms, 

927 _("Successfully updated %(count)d %(items)s.") 

928 % {"count": 1, "items": model_ngettext(BankAccount(), 1)}, 

929 messages.SUCCESS, 

930 ) 

931 

932 

933@freeze_time("2019-01-01") 

934@override_settings(SUSPEND_SIGNALS=True, THALIA_PAY_ENABLED_PAYMENT_METHOD=True) 

935class PaymentUserAdminTest(TestCase): 

936 fixtures = ["members.json", "bank_accounts.json"] 

937 

938 @classmethod 

939 def setUpTestData(cls) -> None: 

940 cls.user = Member.objects.get(pk=2) 

941 

942 def setUp(self) -> None: 

943 self.client = Client() 

944 self.client.force_login(self.user) 

945 self.site = AdminSite() 

946 self.admin = admin.PaymentUserAdmin(PaymentUser, admin_site=self.site) 

947 self.rf = RequestFactory() 

948 self.user = PaymentUser.objects.first() 

949 

950 def test_has_add_permissions(self): 

951 request = self.rf.get(reverse("admin:payments_paymentuser_add")) 

952 request.user = self.user 

953 self.assertFalse(self.admin.has_add_permission(request)) 

954 

955 def test_has_delete_permissions(self): 

956 request = self.rf.get( 

957 reverse("admin:payments_paymentuser_delete", args=[self.user.pk]) 

958 ) 

959 request.user = self.user 

960 self.assertFalse(self.admin.has_delete_permission(request)) 

961 

962 @mock.patch("payments.models.PaymentUser.tpay_balance", new_callable=PropertyMock) 

963 @mock.patch("payments.models.PaymentUser.tpay_enabled", new_callable=PropertyMock) 

964 def test_get_tpay_balance(self, tpay_enabled, tpay_balance): 

965 tpay_balance.return_value = Decimal(-10) 

966 tpay_enabled.return_value = True 

967 self.assertEqual(self.admin.get_tpay_balance(self.user), "€ -10.00") 

968 

969 @mock.patch("payments.models.PaymentUser.tpay_enabled", new_callable=PropertyMock) 

970 def test_get_tpay_enabled(self, tpay_enabled): 

971 tpay_enabled.return_value = True 

972 self.assertTrue(self.admin.get_tpay_enabled(self.user)) 

973 

974 @mock.patch("payments.models.PaymentUser.tpay_allowed", new_callable=PropertyMock) 

975 def test_get_tpay_allowed(self, tpay_allowed): 

976 tpay_allowed.return_value = True 

977 self.assertTrue(self.admin.get_tpay_allowed(self.user)) 

978 

979 def test_get_queryset(self): 

980 self.assertQuerySetEqual( 

981 self.admin.get_queryset(None).all(), 

982 PaymentUser.objects.all(), 

983 ordered=False, 

984 ) 

985 

986 def test_tpay_allowed_filter(self): 

987 filter_all = admin.ThaliaPayAllowedFilter( 

988 None, {}, PaymentUser, admin.PaymentUserAdmin 

989 ) 

990 self.assertQuerySetEqual( 

991 filter_all.queryset( 

992 None, PaymentUser.objects.select_properties("tpay_allowed") 

993 ), 

994 PaymentUser.objects.select_properties("tpay_allowed"), 

995 ordered=False, 

996 ) 

997 

998 filter_true = admin.ThaliaPayAllowedFilter( 

999 None, {"tpay_allowed": "1"}, PaymentUser, admin.PaymentUserAdmin 

1000 ) 

1001 self.assertQuerySetEqual( 

1002 filter_true.queryset( 

1003 None, PaymentUser.objects.select_properties("tpay_allowed") 

1004 ) 

1005 .values_list("pk", flat=True) 

1006 .all(), 

1007 [1, 2, 3, 4, 5], 

1008 ordered=False, 

1009 ) 

1010 filter_false = admin.ThaliaPayAllowedFilter( 

1011 None, {"tpay_allowed": "0"}, PaymentUser, admin.PaymentUserAdmin 

1012 ) 

1013 self.assertQuerySetEqual( 

1014 filter_false.queryset( 

1015 None, PaymentUser.objects.select_properties("tpay_allowed") 

1016 ) 

1017 .values_list("pk", flat=True) 

1018 .all(), 

1019 [], 

1020 ordered=False, 

1021 ) 

1022 

1023 def test_tpay_enabled_filter(self): 

1024 filter_all = admin.ThaliaPayEnabledFilter( 

1025 None, {}, PaymentUser, admin.PaymentUserAdmin 

1026 ) 

1027 self.assertEqual( 

1028 filter_all.queryset(None, PaymentUser.objects), PaymentUser.objects 

1029 ) 

1030 

1031 filter_true = admin.ThaliaPayEnabledFilter( 

1032 None, {"tpay_enabled": "1"}, PaymentUser, admin.PaymentUserAdmin 

1033 ) 

1034 self.assertQuerySetEqual( 

1035 filter_true.queryset(None, PaymentUser.objects) 

1036 .values_list("pk", flat=True) 

1037 .all(), 

1038 [2, 1], 

1039 ordered=False, 

1040 ) 

1041 filter_false = admin.ThaliaPayEnabledFilter( 

1042 None, {"tpay_enabled": "0"}, PaymentUser, admin.PaymentUserAdmin 

1043 ) 

1044 self.assertQuerySetEqual( 

1045 filter_false.queryset(None, PaymentUser.objects) 

1046 .values_list("pk", flat=True) 

1047 .all(), 

1048 [3, 4, 5], 

1049 ordered=False, 

1050 ) 

1051 

1052 def test_tpay_balance_filter(self): 

1053 filter_all = admin.ThaliaPayBalanceFilter( 

1054 None, {}, PaymentUser, admin.PaymentUserAdmin 

1055 ) 

1056 self.assertQuerySetEqual( 

1057 filter_all.queryset( 

1058 None, PaymentUser.objects.select_properties("tpay_balance") 

1059 ), 

1060 PaymentUser.objects.select_properties("tpay_balance"), 

1061 ordered=False, 

1062 ) 

1063 

1064 filter_true = admin.ThaliaPayBalanceFilter( 

1065 None, {"tpay_balance": "0"}, PaymentUser, admin.PaymentUserAdmin 

1066 ) 

1067 self.assertQuerySetEqual( 

1068 filter_true.queryset( 

1069 None, PaymentUser.objects.select_properties("tpay_balance") 

1070 ) 

1071 .values_list("pk", flat=True) 

1072 .all(), 

1073 [1, 2, 3, 4, 5], 

1074 ordered=False, 

1075 ) 

1076 filter_false = admin.ThaliaPayBalanceFilter( 

1077 None, {"tpay_balance": "1"}, PaymentUser, admin.PaymentUserAdmin 

1078 ) 

1079 self.assertQuerySetEqual( 

1080 filter_false.queryset( 

1081 None, PaymentUser.objects.select_properties("tpay_balance") 

1082 ) 

1083 .values_list("pk", flat=True) 

1084 .all(), 

1085 [], 

1086 ordered=False, 

1087 ) 

1088 filter_true = admin.ThaliaPayBalanceFilter( 

1089 None, {"tpay_balance": "0"}, PaymentUser, admin.PaymentUserAdmin 

1090 ) 

1091 self.assertQuerySetEqual( 

1092 filter_true.queryset( 

1093 None, PaymentUser.objects.select_properties("tpay_balance") 

1094 ) 

1095 .values_list("pk", flat=True) 

1096 .all(), 

1097 [1, 2, 3, 4, 5], 

1098 ordered=False, 

1099 ) 

1100 filter_false = admin.ThaliaPayBalanceFilter( 

1101 None, {"tpay_balance": "1"}, PaymentUser, admin.PaymentUserAdmin 

1102 ) 

1103 self.assertQuerySetEqual( 

1104 filter_false.queryset( 

1105 None, PaymentUser.objects.select_properties("tpay_balance") 

1106 ) 

1107 .values_list("pk", flat=True) 

1108 .all(), 

1109 [], 

1110 ordered=False, 

1111 ) 

1112 

1113 def test_user_link(self): 

1114 self.assertEqual( 

1115 self.admin.user_link(self.user), 

1116 f"<a href='/admin/auth/user/{self.user.pk}/change/'>{self.user.get_full_name()}</a>", 

1117 ) 

1118 

1119 def test_bankaccount_inline_permissions(self): 

1120 request = self.rf.get(reverse("admin:payments_paymentuser_add")) 

1121 request.user = self.user 

1122 self.assertFalse( 

1123 BankAccountInline(BankAccount, self.admin.admin_site).has_add_permission( 

1124 request 

1125 ) 

1126 ) 

1127 self.assertFalse( 

1128 BankAccountInline(BankAccount, self.admin.admin_site).has_change_permission( 

1129 request 

1130 ) 

1131 ) 

1132 self.assertFalse( 

1133 BankAccountInline(BankAccount, self.admin.admin_site).has_delete_permission( 

1134 request 

1135 ) 

1136 ) 

1137 

1138 def test_payment_inline_permissions(self): 

1139 request = self.rf.get(reverse("admin:payments_paymentuser_add")) 

1140 request.user = self.user 

1141 self.assertFalse( 

1142 PaymentInline(Payment, self.admin.admin_site).has_add_permission(request) 

1143 ) 

1144 self.assertFalse( 

1145 PaymentInline(Payment, self.admin.admin_site).has_change_permission(request) 

1146 ) 

1147 self.assertFalse( 

1148 PaymentInline(Payment, self.admin.admin_site).has_delete_permission(request) 

1149 ) 

1150 

1151 def test_disallow_tpay_action(self): 

1152 request = self.rf.get(reverse("admin:payments_paymentuser_changelist")) 

1153 request.user = self.user 

1154 with patch("payments.models.PaymentUser.disallow_tpay") as mock: 

1155 request._messages = Mock() 

1156 self.admin.disallow_thalia_pay(request, PaymentUser.objects.all()) 

1157 mock.assert_called() 

1158 

1159 def test_allow_tpay_action(self): 

1160 request = self.rf.get(reverse("admin:payments_paymentuser_changelist")) 

1161 request.user = self.user 

1162 with patch("payments.models.PaymentUser.allow_tpay") as mock: 

1163 request._messages = Mock() 

1164 self.admin.allow_thalia_pay(request, PaymentUser.objects.all()) 

1165 mock.assert_called() 

1166 

1167 def test_paymentuser_two_bankaccounts(self): 

1168 p = PaymentUser.objects.get(pk=self.user.pk) 

1169 self.user.is_superuser = True 

1170 self.user.save() 

1171 self.client = Client() 

1172 self.client.force_login(self.user) 

1173 response = self.client.get(f"/admin/payments/paymentuser/{p.pk}/change/") 

1174 self.assertEqual(response.status_code, 200) 

1175 b1 = BankAccount.objects.create( 

1176 owner=p, 

1177 initials="J", 

1178 last_name="Test2", 

1179 iban="NL91ABNA0417164300", 

1180 mandate_no="test1", 

1181 valid_from=timezone.now().date() - timezone.timedelta(days=5), 

1182 valid_until=timezone.now().date() - timezone.timedelta(days=3), 

1183 last_used=timezone.now().date() - timezone.timedelta(days=4), 

1184 signature="base64,png", 

1185 ) 

1186 b2 = BankAccount.objects.create( 

1187 owner=p, 

1188 initials="J", 

1189 last_name="Test2", 

1190 iban="NL91ABNA0417164300", 

1191 mandate_no="test2", 

1192 valid_from=timezone.now().date() - timezone.timedelta(days=3), 

1193 last_used=timezone.now().date() - timezone.timedelta(days=2), 

1194 signature="base64,png", 

1195 ) 

1196 response = self.client.get(f"/admin/payments/paymentuser/{p.pk}/change/") 

1197 self.assertEqual(response.status_code, 200)