Verified Commit dee85827 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Add more tests

parent dbea8c5a
"""Registers admin interfaces for the payments module"""
import csv
from collections import OrderedDict
from django.contrib import admin, messages
from django.contrib.admin import ModelAdmin
from django.contrib.admin.utils import model_ngettext
from django.db.models import QuerySet
from django.db.models.query_utils import Q
from django.http import HttpResponse
from django.http import HttpResponse, HttpRequest
from django.urls import path, reverse
from django.utils import timezone
from django.utils.html import format_html
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _
from members.models import Member
from payments import services, admin_views
from payments.forms import BankAccountAdminForm
from .models import Payment, BankAccount
def _show_message(admin, request, n, message, error):
def _show_message(admin: ModelAdmin, request: HttpRequest, n: int, message: str, error: str) -> None:
if n == 0:
admin.message_user(request, error, messages.ERROR)
else:
......@@ -50,7 +54,7 @@ class PaymentAdmin(admin.ModelAdmin):
'process_wire_selected', 'export_csv']
@staticmethod
def _member_link(member):
def _member_link(member: Member) -> str:
if member:
return format_html("<a href='{}'>{}</a>",
member.get_absolute_url(),
......@@ -58,18 +62,19 @@ class PaymentAdmin(admin.ModelAdmin):
else:
return "-"
def paid_by_link(self, obj):
def paid_by_link(self, obj: Payment) -> str:
return self._member_link(obj.paid_by)
paid_by_link.admin_order_field = 'paid_by'
paid_by_link.short_description = _('paid by')
def processed_by_link(self, obj):
def processed_by_link(self, obj: Payment) -> str:
return self._member_link(obj.processed_by)
processed_by_link.admin_order_field = 'processed_by'
processed_by_link.short_description = _('processed by')
def changeform_view(self, request, object_id=None, form_url='',
extra_context=None):
def changeform_view(self, request: HttpRequest, object_id: int = None,
form_url: str = '', extra_context: dict = None
) -> HttpResponse:
"""
Renders the change formview
Only allow when the payment has not been processed yet
......@@ -83,12 +88,12 @@ class PaymentAdmin(admin.ModelAdmin):
return super().changeform_view(
request, object_id, form_url, {'payment': obj})
def get_readonly_fields(self, request, obj=None):
def get_readonly_fields(self, request: HttpRequest, obj: Payment = None):
if not obj:
return 'created_at', 'type', 'processing_date', 'processed_by'
return super().get_readonly_fields(request, obj)
def get_actions(self, request):
def get_actions(self, request: HttpRequest) -> OrderedDict:
"""Get the actions for the payments"""
"""Hide the processing actions if the right permissions are missing"""
actions = super().get_actions(request)
......@@ -98,7 +103,8 @@ class PaymentAdmin(admin.ModelAdmin):
del(actions['process_wire_selected'])
return actions
def process_cash_selected(self, request, queryset):
def process_cash_selected(self, request: HttpRequest,
queryset: QuerySet) -> None:
"""Process the selected payment as cash"""
if request.user.has_perm('payments.process_payments'):
updated_payments = services.process_payment(
......@@ -108,7 +114,8 @@ class PaymentAdmin(admin.ModelAdmin):
process_cash_selected.short_description = _(
'Process selected payments (cash)')
def process_card_selected(self, request, queryset):
def process_card_selected(self, request: HttpRequest,
queryset: QuerySet) -> None:
"""Process the selected payment as card"""
if request.user.has_perm('payments.process_payments'):
updated_payments = services.process_payment(
......@@ -118,7 +125,8 @@ class PaymentAdmin(admin.ModelAdmin):
process_card_selected.short_description = _(
'Process selected payments (card)')
def process_wire_selected(self, request, queryset):
def process_wire_selected(self, request: HttpRequest,
queryset: QuerySet) -> None:
"""Process the selected payment as wire"""
if request.user.has_perm('payments.process_payments'):
updated_payments = services.process_payment(
......@@ -128,7 +136,7 @@ class PaymentAdmin(admin.ModelAdmin):
process_wire_selected.short_description = _(
'Process selected payments (wire)')
def _process_feedback(self, request, updated_payments):
def _process_feedback(self, request, updated_payments: list) -> None:
"""Show a feedback message for the processing result"""
rows_updated = len(updated_payments)
_show_message(
......@@ -137,7 +145,7 @@ class PaymentAdmin(admin.ModelAdmin):
error=_('The selected payment(s) could not be processed.')
)
def get_urls(self):
def get_urls(self) -> list:
urls = super().get_urls()
custom_urls = [
path('<uuid:pk>/process/',
......@@ -147,7 +155,13 @@ class PaymentAdmin(admin.ModelAdmin):
]
return custom_urls + urls
def export_csv(self, request, queryset):
def export_csv(self, request: HttpRequest,
queryset: QuerySet) -> HttpResponse:
"""
Export a CSV of payments
:param request: Request
:param queryset: Items to be exported
"""
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment;\
filename="payments.csv"'
......@@ -179,18 +193,18 @@ class ValidAccountFilter(admin.SimpleListFilter):
title = _('mandates')
parameter_name = 'active'
def lookups(self, request, model_name):
def lookups(self, request, model_name) -> tuple:
return (
('valid', _('Valid')),
('invalid', _('Invalid')),
('none', _('None')),
)
def queryset(self, request, queryset):
def queryset(self, request, queryset) -> QuerySet:
now = timezone.now()
if self.value() == 'valid':
return queryset.filter(Q(valid_from__gte=now) &
return queryset.filter(Q(valid_from__lte=now) &
Q(valid_until=None) |
Q(valid_until__lt=now))
......@@ -200,6 +214,8 @@ class ValidAccountFilter(admin.SimpleListFilter):
if self.value() == 'none':
return queryset.filter(valid_from=None)
return queryset
@admin.register(BankAccount)
class BankAccountAdmin(admin.ModelAdmin):
......@@ -215,7 +231,7 @@ class BankAccountAdmin(admin.ModelAdmin):
'owner__last_name', 'iban')
form = BankAccountAdminForm
def owner_link(self, obj):
def owner_link(self, obj: BankAccount) -> str:
if obj.owner:
return format_html("<a href='{}'>{}</a>",
reverse('admin:auth_user_change',
......@@ -225,13 +241,14 @@ class BankAccountAdmin(admin.ModelAdmin):
owner_link.admin_order_field = 'owner'
owner_link.short_description = _('owner')
def export_csv(self, request, queryset):
def export_csv(self, request: HttpRequest,
queryset: QuerySet) -> HttpResponse:
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment;\
filename="accounts.csv"'
writer = csv.writer(response)
headers = [_('created'), _('name'), _('reference'), _('iban'),
_('bic'), _('valid from'), _('valid until'), _('signature')]
headers = [_('created'), _('name'), _('reference'), _('IBAN'),
_('BIC'), _('valid from'), _('valid until'), _('signature')]
writer.writerow([capfirst(x) for x in headers])
for account in queryset:
writer.writerow([
......
"""The services defined by the payments package"""
from django.db.models import QuerySet
from members.models import Member
from .models import Payment
def process_payment(queryset, processed_by, pay_type=Payment.CARD):
def process_payment(queryset: QuerySet, processed_by: Member,
pay_type: str = Payment.CARD) -> list:
"""
Process the payment
......
......@@ -9,17 +9,20 @@ from django.contrib.contenttypes.models import ContentType
from django.http import HttpRequest
from django.test import TestCase, SimpleTestCase, Client, RequestFactory
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from freezegun import freeze_time
from members.models import Member, Profile
from payments import admin
from payments.models import Payment
from payments.admin import ValidAccountFilter
from payments.models import Payment, BankAccount
class GlobalAdminTest(SimpleTestCase):
@mock.patch('registrations.admin.RegistrationAdmin')
def test_show_message(self, admin_mock):
def test_show_message(self, admin_mock) -> None:
admin_mock.return_value = admin_mock
request = Mock(spec=HttpRequest)
......@@ -35,7 +38,7 @@ class GlobalAdminTest(SimpleTestCase):
class PaymentAdminTest(TestCase):
@classmethod
def setUpTestData(cls):
def setUpTestData(cls) -> None:
cls.user = Member.objects.create(
username='test1',
first_name='Test1',
......@@ -45,7 +48,7 @@ class PaymentAdminTest(TestCase):
)
Profile.objects.create(user=cls.user)
def setUp(self):
def setUp(self) -> None:
self.client = Client()
self.client.force_login(self.user)
self.factory = RequestFactory()
......@@ -60,7 +63,10 @@ class PaymentAdminTest(TestCase):
self.client.logout()
self.client.force_login(self.user)
def _give_user_permissions(self):
def _give_user_permissions(self) -> None:
"""
Helper to give the user permissions
"""
content_type = ContentType.objects.get_for_model(Payment)
permissions = Permission.objects.filter(
content_type__app_label=content_type.app_label,
......@@ -73,7 +79,10 @@ class PaymentAdminTest(TestCase):
self.client.force_login(self.user)
@mock.patch('payments.models.Payment.objects.get')
def test_changeform_view(self, payment_get):
def test_changeform_view(self, payment_get) -> None:
"""
Tests that the right context data is added to the response
"""
object_id = 'c85ea333-3508-46f1-8cbb-254f8c138020'
payment = Payment.objects.create(pk=object_id,
amount=7.5)
......@@ -105,29 +114,40 @@ class PaymentAdminTest(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['payment'], None)
def test_paid_by_link(self):
object_id = 'c85ea333-3508-46f1-8cbb-254f8c138020'
payment = Payment.objects.create(pk=object_id,
amount=7.5,
paid_by=self.user)
def test_paid_by_link(self) -> None:
"""
Tests that the right link for the paying user is returned
"""
payment = Payment.objects.create(
amount=7.5, paid_by=self.user)
self.assertEqual(self.admin.paid_by_link(payment),
f"<a href='/members/profile/{self.user.pk}'>"
f"Test1 Example</a>")
def test_processed_by_link(self):
object_id = 'c85ea333-3508-46f1-8cbb-254f8c138020'
payment = Payment.objects.create(pk=object_id,
amount=7.5,
processed_by=self.user)
payment2 = Payment.objects.create(amount=7.5)
self.assertEqual(self.admin.paid_by_link(payment2), '-')
self.assertEqual(self.admin.processed_by_link(payment),
def test_processed_by_link(self) -> None:
"""
Tests that the right link for the processing user is returned
"""
payment1 = Payment.objects.create(
amount=7.5, processed_by=self.user)
self.assertEqual(self.admin.processed_by_link(payment1),
f"<a href='/members/profile/{self.user.pk}'>"
f"Test1 Example</a>")
payment2 = Payment.objects.create(amount=7.5)
self.assertEqual(self.admin.processed_by_link(payment2), '-')
@mock.patch('django.contrib.admin.ModelAdmin.message_user')
@mock.patch('payments.services.process_payment')
def test_process_cash(self, process_payment, message_user):
def test_process_cash(self, process_payment, message_user) -> None:
"""
Tests that a cash payment is processed correctly
"""
object_id = 'c85ea333-3508-46f1-8cbb-254f8c138020'
payment = Payment.objects.create(pk=object_id,
amount=7.5)
......@@ -167,7 +187,10 @@ class PaymentAdminTest(TestCase):
@mock.patch('django.contrib.admin.ModelAdmin.message_user')
@mock.patch('payments.services.process_payment')
def test_process_card(self, process_payment, message_user):
def test_process_card(self, process_payment, message_user) -> None:
"""
Tests that a card payment is processed correctly
"""
object_id = 'c85ea333-3508-46f1-8cbb-254f8c138020'
payment = Payment.objects.create(pk=object_id,
amount=7.5)
......@@ -207,7 +230,10 @@ class PaymentAdminTest(TestCase):
@mock.patch('django.contrib.admin.ModelAdmin.message_user')
@mock.patch('payments.services.process_payment')
def test_process_wire(self, process_payment, message_user):
def test_process_wire(self, process_payment, message_user) -> None:
"""
Tests that a wire payment is processed correctly
"""
object_id = 'c85ea333-3508-46f1-8cbb-254f8c138020'
payment = Payment.objects.create(pk=object_id,
amount=7.5)
......@@ -245,7 +271,10 @@ class PaymentAdminTest(TestCase):
}, messages.SUCCESS
)
def test_get_actions(self):
def test_get_actions(self) -> None:
"""
Test that the actions are added to the admin
"""
response = self.client.get(
reverse('admin:payments_payment_changelist'))
......@@ -263,6 +292,238 @@ class PaymentAdminTest(TestCase):
'process_wire_selected',
'export_csv'])
def test_get_urls(self):
def test_get_urls(self) -> None:
"""
Test that the custom urls are added to the admin
"""
urls = self.admin.get_urls()
self.assertEqual(urls[0].name, 'payments_payment_process')
@freeze_time('2019-01-01')
def test_export_csv(self):
"""
Test that the CSV export of payments is correct
"""
Payment.objects.create(
amount=7.5,
processed_by=self.user,
paid_by=self.user,
type=Payment.CARD
).save()
Payment.objects.create(
amount=17.5,
processed_by=self.user,
paid_by=self.user,
type=Payment.CASH
).save()
Payment.objects.create(
amount=9,
notes='This is a test'
).save()
response = self.admin.export_csv(HttpRequest(), Payment.objects.all())
self.assertEqual(
b'Created,Processed,Amount,Type,Processor,Payer id,Payer name,'
b'Notes\r\n2019-01-01 00:00:00+00:00,2019-01-01 00:00:00+00:00,'
b'7.50,Card payment,Test1 Example,1,Test1 Example,\r\n2019-01-01 '
b'00:00:00+00:00,2019-01-01 00:00:00+00:00,17.50,Cash payment,'
b'Test1 Example,1,Test1 Example,\r\n2019-01-01 00:00:00+00:00,,'
b'9.00,No payment,-,-,-,This is a test\r\n',
response.content
)
@freeze_time('2019-01-01')
class ValidAccountFilterTest(TestCase):
@classmethod
def setUpTestData(cls) -> None:
cls.member = Member.objects.create(
username='test1',
first_name='Test1',
last_name='Example',
email='test1@example.org',
is_staff=True,
)
Profile.objects.create(user=cls.member)
cls.no_mandate = BankAccount.objects.create(
owner=cls.member,
initials='J',
last_name='Test',
iban='NL91ABNA0417164300'
)
cls.valid_mandate = BankAccount.objects.create(
owner=cls.member,
initials='J',
last_name='Test',
iban='NL91ABNA0417164300',
mandate_no='11-1',
valid_from=timezone.now().date() - timezone.timedelta(days=5),
signature='base64,png'
)
cls.invalid_mandate = BankAccount.objects.create(
owner=cls.member,
initials='J',
last_name='Test',
iban='NL91ABNA0417164300',
mandate_no='11-2',
valid_from=timezone.now().date() - timezone.timedelta(days=5),
valid_until=timezone.now().date(),
signature='base64,png'
)
def setUp(self) -> None:
self.site = AdminSite()
self.admin = admin.BankAccountAdmin(BankAccount, admin_site=self.site)
def test_lookups(self):
"""
Tests that the right options are implemented for lookups
"""
account_filter = ValidAccountFilter(
model=BankAccount,
model_admin=self.admin,
params={},
request=None
)
self.assertEqual((
('valid', 'Valid'),
('invalid', 'Invalid'),
('none', 'None'),
), account_filter.lookups(None, None))
def test_queryset(self):
"""
Tests that the right results are returned
"""
for param, item in [
('valid', self.valid_mandate),
('invalid', self.invalid_mandate),
('none', self.no_mandate),
]:
with self.subTest(f'Status {param}'):
account_filter = ValidAccountFilter(
model=BankAccount,
model_admin=self.admin,
params={
'active': param
},
request=None
)
result = account_filter.queryset(
None, BankAccount.objects.all())
self.assertEqual(result.count(), 1)
self.assertEqual(result.first().pk, item.pk)
with self.subTest('No known param'):
account_filter = ValidAccountFilter(
model=BankAccount,
model_admin=self.admin,
params={
'active': 'bla'
},
request=None
)
result = account_filter.queryset(
None, BankAccount.objects.all())
self.assertEqual(result.count(), 3)
@freeze_time('2019-01-01')
class BankAccountAdminTest(TestCase):
@classmethod
def setUpTestData(cls) -> None:
cls.user = Member.objects.create(
username='test1',
first_name='Test1',
last_name='Example',
email='test1@example.org',
is_staff=True,
)
Profile.objects.create(user=cls.user)
def setUp(self) -> None:
self.site = AdminSite()
self.admin = admin.BankAccountAdmin(BankAccount, admin_site=self.site)
def test_owner_link(self) -> None:
"""
Test that the link to a member profile is correct
"""
bank_account1 = BankAccount.objects.create(
owner=self.user,
initials='J',
last_name='Test',
iban='NL91ABNA0417164300'
)
self.assertEqual(self.admin.owner_link(bank_account1),
f"<a href='/admin/auth/user/{self.user.pk}/change/'>"
f"Test1 Example</a>")
bank_account2 = BankAccount.objects.create(
owner=None,
initials='J2',
last_name='Test',
iban='NL91ABNA0417164300'
)
self.assertEqual(self.admin.owner_link(bank_account2), '')
def test_export_csv(self):
"""
Test that the CSV export of accounts is correct
"""
BankAccount.objects.create(
owner=self.user,
initials='J',
last_name='Test',
iban='NL91ABNA0417164300'
)
BankAccount.objects.create(
owner=self.user,
initials='J2',
last_name='Test',
iban='NL91ABNA0417164300'
)
BankAccount.objects.create(
owner=self.user,
initials='J3',
last_name='Test',
iban='NL91ABNA0417164300',
mandate_no='12-1',
valid_from=timezone.now().date() - timezone.timedelta(days=5),
valid_until=timezone.now().date(),
signature='sig'
)
BankAccount.objects.create(
owner=self.user,
initials='J4',
last_name='Test',
iban='DE12500105170648489890',
bic='NBBEBEBB',
mandate_no='11-1',
valid_from=timezone.now().date(),
valid_until=timezone.now().date() + timezone.timedelta(days=5),
signature='sig'
)
response = self.admin.export_csv(HttpRequest(),
BankAccount.objects.all())
self.assertEqual(
b'Created,Name,Reference,IBAN,BIC,Valid from,Valid until,'
b'Signature\r\n2019-01-01 00:00:00+00:00,J Test,,'
b'NL91ABNA0417164300,,,,\r\n2019-01-01 00:00:00+00:00,J2 Test,,'
b'NL91ABNA0417164300,,,,\r\n2019-01-01 00:00:00+00:00,J3 Test,'
b'12-1,NL91ABNA0417164300,,2018-12-27,2019-01-01,'
b'sig\r\n2019-01-01 00:00:00+00:00,J4 Test,11-1,'
b'DE12500105170648489890,NBBEBEBB,2019-01-01,2019-01-06,sig\r\n',
response.content
)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment