You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
705 lines
31 KiB
Python
705 lines
31 KiB
Python
import unittest
|
|
from http import HTTPStatus
|
|
from django.test import TestCase, override_settings
|
|
from django.contrib.admin.sites import AdminSite
|
|
from django.test import RequestFactory, Client
|
|
from django.contrib.auth.models import User, Permission
|
|
from django.utils import timezone
|
|
from django.contrib.sessions.middleware import SessionMiddleware
|
|
from django.contrib.messages.middleware import MessageMiddleware
|
|
from django.contrib.messages.storage.fallback import FallbackStorage
|
|
from django.contrib.messages import get_messages
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.urls import reverse, reverse_lazy
|
|
from django.http import HttpResponseRedirect, HttpResponse
|
|
from unittest.mock import Mock, patch
|
|
from django.test.utils import override_settings
|
|
from django.urls import path, include
|
|
from django.contrib import admin as django_admin
|
|
|
|
from members.tests.utils import create_custom_user
|
|
from members.models import Member, MALE, Freizeit, GEMEINSCHAFTS_TOUR, MUSKELKRAFT_ANREISE
|
|
from ..models import (
|
|
Ledger, Statement, StatementUnSubmitted, StatementConfirmed, Transaction, Bill,
|
|
StatementSubmitted
|
|
)
|
|
from ..admin import (
|
|
LedgerAdmin, StatementUnSubmittedAdmin, StatementSubmittedAdmin,
|
|
StatementConfirmedAdmin, TransactionAdmin, BillAdmin
|
|
)
|
|
|
|
|
|
class AdminTestCase(TestCase):
|
|
def setUp(self, model, admin):
|
|
self.factory = RequestFactory()
|
|
self.model = model
|
|
if model is not None and admin is not None:
|
|
self.admin = admin(model, AdminSite())
|
|
superuser = User.objects.create_superuser(
|
|
username='superuser', password='secret'
|
|
)
|
|
standard = create_custom_user('standard', ['Standard'], 'Paul', 'Wulter')
|
|
trainer = create_custom_user('trainer', ['Standard', 'Trainings'], 'Lise', 'Lotte')
|
|
treasurer = create_custom_user('treasurer', ['Standard', 'Finance'], 'Lara', 'Litte')
|
|
materialwarden = create_custom_user('materialwarden', ['Standard', 'Material'], 'Loro', 'Lutte')
|
|
|
|
def _login(self, name):
|
|
c = Client()
|
|
res = c.login(username=name, password='secret')
|
|
# make sure we logged in
|
|
assert res
|
|
return c
|
|
|
|
|
|
class StatementUnSubmittedAdminTestCase(AdminTestCase):
|
|
"""Test cases for StatementUnSubmittedAdmin"""
|
|
|
|
def setUp(self):
|
|
super().setUp(model=StatementUnSubmitted, admin=StatementUnSubmittedAdmin)
|
|
|
|
self.superuser = User.objects.get(username='superuser')
|
|
self.member = Member.objects.create(
|
|
prename="Test", lastname="User", birth_date=timezone.now().date(),
|
|
email="test@example.com", gender=MALE, user=self.superuser
|
|
)
|
|
|
|
self.statement = StatementUnSubmitted.objects.create(
|
|
short_description='Test Statement',
|
|
explanation='Test explanation',
|
|
night_cost=25
|
|
)
|
|
|
|
# Create excursion for testing
|
|
self.excursion = Freizeit.objects.create(
|
|
name='Test Excursion',
|
|
kilometers_traveled=100,
|
|
tour_type=GEMEINSCHAFTS_TOUR,
|
|
tour_approach=MUSKELKRAFT_ANREISE,
|
|
difficulty=1
|
|
)
|
|
|
|
# Create confirmed statement with excursion
|
|
self.statement_with_excursion = StatementUnSubmitted.objects.create(
|
|
short_description='With Excursion',
|
|
explanation='Test explanation',
|
|
night_cost=25,
|
|
excursion=self.excursion,
|
|
)
|
|
|
|
def test_save_model_with_member(self):
|
|
"""Test save_model sets created_by for new objects"""
|
|
request = self.factory.post('/')
|
|
request.user = self.superuser
|
|
|
|
# Test with change=False (new object)
|
|
new_statement = Statement(short_description='New Statement')
|
|
self.admin.save_model(request, new_statement, None, change=False)
|
|
self.assertEqual(new_statement.created_by, self.member)
|
|
|
|
def test_get_readonly_fields_submitted(self):
|
|
"""Test readonly fields when statement is submitted"""
|
|
# Mark statement as submitted
|
|
self.statement.status = Statement.SUBMITTED
|
|
readonly_fields = self.admin.get_readonly_fields(None, self.statement)
|
|
self.assertIn('status', readonly_fields)
|
|
self.assertIn('excursion', readonly_fields)
|
|
self.assertIn('short_description', readonly_fields)
|
|
|
|
def test_get_readonly_fields_not_submitted(self):
|
|
"""Test readonly fields when statement is not submitted"""
|
|
readonly_fields = self.admin.get_readonly_fields(None, self.statement)
|
|
self.assertEqual(readonly_fields, ['status', 'excursion'])
|
|
|
|
def test_submit_view_insufficient_permission(self):
|
|
url = reverse('admin:finance_statementunsubmitted_submit',
|
|
args=(self.statement.pk,))
|
|
c = self._login('standard')
|
|
response = c.get(url, follow=True)
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
self.assertContains(response, _('Insufficient permissions.'))
|
|
|
|
def test_submit_view_get(self):
|
|
url = reverse('admin:finance_statementunsubmitted_submit',
|
|
args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.get(url, follow=True)
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
self.assertContains(response, _('Submit statement'))
|
|
|
|
def test_submit_view_get_with_excursion(self):
|
|
url = reverse('admin:finance_statementunsubmitted_submit',
|
|
args=(self.statement_with_excursion.pk,))
|
|
c = self._login('superuser')
|
|
response = c.get(url, follow=True)
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
self.assertContains(response, _('Finance overview'))
|
|
|
|
def test_submit_view_post(self):
|
|
url = reverse('admin:finance_statementunsubmitted_submit',
|
|
args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'apply': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
text = _("Successfully submited %(name)s. The finance department will notify the requestors as soon as possible.") % {'name': str(self.statement)}
|
|
self.assertContains(response, text)
|
|
|
|
|
|
class StatementSubmittedAdminTestCase(AdminTestCase):
|
|
"""Test cases for StatementSubmittedAdmin"""
|
|
|
|
def setUp(self):
|
|
super().setUp(model=StatementSubmitted, admin=StatementSubmittedAdmin)
|
|
|
|
self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
|
|
self.member = Member.objects.create(
|
|
prename="Test", lastname="User", birth_date=timezone.now().date(),
|
|
email="test@example.com", gender=MALE, user=self.user
|
|
)
|
|
|
|
self.finance_user = User.objects.create_user('finance', 'finance@example.com', 'pass')
|
|
finance_perm = Permission.objects.get(codename='process_statementsubmitted')
|
|
self.finance_user.user_permissions.add(finance_perm)
|
|
|
|
self.statement = Statement.objects.create(
|
|
short_description='Submitted Statement',
|
|
explanation='Test explanation',
|
|
status=Statement.SUBMITTED,
|
|
submitted_by=self.member,
|
|
submitted_date=timezone.now(),
|
|
night_cost=25
|
|
)
|
|
self.statement_unsubmitted = StatementUnSubmitted.objects.create(
|
|
short_description='Submitted Statement',
|
|
explanation='Test explanation',
|
|
night_cost=25
|
|
)
|
|
self.transaction = Transaction.objects.create(
|
|
reference='verylonglong' * 14,
|
|
amount=3,
|
|
statement=self.statement,
|
|
member=self.member,
|
|
)
|
|
|
|
# Create commonly used test objects
|
|
self.ledger = Ledger.objects.create(name='Test Ledger')
|
|
self.excursion = Freizeit.objects.create(
|
|
name='Test Excursion',
|
|
kilometers_traveled=100,
|
|
tour_type=GEMEINSCHAFTS_TOUR,
|
|
tour_approach=MUSKELKRAFT_ANREISE,
|
|
difficulty=1
|
|
)
|
|
self.other_member = Member.objects.create(
|
|
prename="Other", lastname="Member", birth_date=timezone.now().date(),
|
|
email="other@example.com", gender=MALE
|
|
)
|
|
|
|
# Create statements for generate transactions tests
|
|
self.statement_no_trans_success = Statement.objects.create(
|
|
short_description='No Transactions Success',
|
|
explanation='Test explanation',
|
|
status=Statement.SUBMITTED,
|
|
submitted_by=self.member,
|
|
submitted_date=timezone.now(),
|
|
night_cost=25
|
|
)
|
|
self.statement_no_trans_error = Statement.objects.create(
|
|
short_description='No Transactions Error',
|
|
explanation='Test explanation',
|
|
status=Statement.SUBMITTED,
|
|
submitted_by=self.member,
|
|
submitted_date=timezone.now(),
|
|
night_cost=25
|
|
)
|
|
|
|
# Create bills for generate transactions tests
|
|
self.bill_for_success = Bill.objects.create(
|
|
statement=self.statement_no_trans_success,
|
|
short_description='Test Bill Success',
|
|
amount=50,
|
|
paid_by=self.member,
|
|
costs_covered=True
|
|
)
|
|
self.bill_for_error = Bill.objects.create(
|
|
statement=self.statement_no_trans_error,
|
|
short_description='Test Bill Error',
|
|
amount=50,
|
|
paid_by=None, # No payer will cause generate_transactions to fail
|
|
costs_covered=True,
|
|
)
|
|
|
|
def _create_matching_bill(self, statement=None, amount=None):
|
|
"""Helper method to create a bill that matches transaction amount"""
|
|
return Bill.objects.create(
|
|
statement=statement or self.statement,
|
|
short_description='Test Bill',
|
|
amount=amount or self.transaction.amount,
|
|
paid_by=self.member,
|
|
costs_covered=True
|
|
)
|
|
|
|
def _create_non_matching_bill(self, statement=None, amount=100):
|
|
"""Helper method to create a bill that doesn't match transaction amount"""
|
|
return Bill.objects.create(
|
|
statement=statement or self.statement,
|
|
short_description='Non-matching Bill',
|
|
amount=amount,
|
|
paid_by=self.member
|
|
)
|
|
|
|
def test_has_add_permission(self):
|
|
"""Test that add permission is disabled"""
|
|
request = self.factory.get('/')
|
|
request.user = self.finance_user
|
|
self.assertFalse(self.admin.has_add_permission(request))
|
|
|
|
def test_has_change_permission_with_permission(self):
|
|
"""Test change permission with proper permission"""
|
|
request = self.factory.get('/')
|
|
request.user = self.finance_user
|
|
self.assertTrue(self.admin.has_change_permission(request))
|
|
|
|
def test_has_change_permission_without_permission(self):
|
|
"""Test change permission without proper permission"""
|
|
request = self.factory.get('/')
|
|
request.user = self.user
|
|
self.assertFalse(self.admin.has_change_permission(request))
|
|
|
|
def test_has_delete_permission(self):
|
|
"""Test that delete permission is disabled"""
|
|
request = self.factory.get('/')
|
|
request.user = self.finance_user
|
|
self.assertFalse(self.admin.has_delete_permission(request))
|
|
|
|
def test_readonly_fields(self):
|
|
self.assertNotIn('explanation',
|
|
self.admin.get_readonly_fields(None, self.statement_unsubmitted))
|
|
|
|
def test_change(self):
|
|
url = reverse('admin:finance_statementsubmitted_change',
|
|
args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.get(url)
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
|
|
def test_overview_view(self):
|
|
url = reverse('admin:finance_statementsubmitted_overview',
|
|
args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.get(url)
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
self.assertContains(response, _('View submitted statement'))
|
|
|
|
def test_overview_view_statement_not_found(self):
|
|
"""Test overview_view with statement that can't be found in StatementSubmitted queryset"""
|
|
# When trying to access an unsubmitted statement via StatementSubmitted admin,
|
|
# the decorator will fail to find it and show "Statement not found"
|
|
self.statement.status = Statement.UNSUBMITTED
|
|
self.statement.save()
|
|
|
|
url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.get(url, follow=True)
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
messages = list(get_messages(response.wsgi_request))
|
|
expected_text = str(_("Statement not found."))
|
|
self.assertTrue(any(expected_text in str(msg) for msg in messages))
|
|
|
|
def test_overview_view_transaction_execution_confirm(self):
|
|
"""Test overview_view transaction execution confirm"""
|
|
# Set up statement to be valid for confirmation
|
|
self.transaction.ledger = self.ledger
|
|
self.transaction.save()
|
|
|
|
# Create a bill that matches the transaction amount to make it valid
|
|
self._create_matching_bill()
|
|
|
|
url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'transaction_execution_confirm': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
success_text = _("Successfully confirmed %(name)s. I hope you executed the associated transactions, I wont remind you again.") % {'name': str(self.statement)}
|
|
self.assertContains(response, success_text)
|
|
self.statement.refresh_from_db()
|
|
self.assertTrue(self.statement.confirmed)
|
|
|
|
def test_overview_view_transaction_execution_confirm_and_send(self):
|
|
"""Test overview_view transaction execution confirm and send"""
|
|
# Set up statement to be valid for confirmation
|
|
self.transaction.ledger = self.ledger
|
|
self.transaction.save()
|
|
|
|
# Create a bill that matches the transaction amount to make it valid
|
|
self._create_matching_bill()
|
|
|
|
url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'transaction_execution_confirm_and_send': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
success_text = _("Successfully sent receipt to the office.")
|
|
self.assertContains(response, success_text)
|
|
|
|
def test_overview_view_confirm_valid(self):
|
|
"""Test overview_view confirm with valid statement"""
|
|
# Create a statement with valid configuration
|
|
# Set up transaction with ledger to make it valid
|
|
self.transaction.ledger = self.ledger
|
|
self.transaction.save()
|
|
|
|
# Create a bill that matches the transaction amount to make total valid
|
|
self._create_matching_bill()
|
|
|
|
url = reverse('admin:finance_statementsubmitted_overview',
|
|
args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, data={'confirm': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
self.assertContains(response, _('Statement confirmed'))
|
|
|
|
def test_overview_view_confirm_non_matching_transactions(self):
|
|
"""Test overview_view confirm with non-matching transactions"""
|
|
# Create a bill that doesn't match the transaction
|
|
self._create_non_matching_bill()
|
|
|
|
url = reverse('admin:finance_statementsubmitted_overview',
|
|
args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'confirm': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
error_text = _("Transactions do not match the covered expenses. Please correct the mistakes listed below.")
|
|
self.assertContains(response, error_text)
|
|
|
|
def test_overview_view_confirm_missing_ledger(self):
|
|
"""Test overview_view confirm with missing ledger"""
|
|
# Ensure transaction has no ledger (ledger=None)
|
|
self.transaction.ledger = None
|
|
self.transaction.save()
|
|
|
|
# Create a bill that matches the transaction amount to pass the first check
|
|
self._create_matching_bill()
|
|
|
|
url = reverse('admin:finance_statementsubmitted_overview',
|
|
args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'confirm': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
# Check the Django messages for the error
|
|
messages = list(get_messages(response.wsgi_request))
|
|
expected_text = str(_("Some transactions have no ledger configured. Please fill in the gaps."))
|
|
self.assertTrue(any(expected_text in str(msg) for msg in messages))
|
|
|
|
def test_overview_view_confirm_invalid_allowance_to(self):
|
|
"""Test overview_view confirm with invalid allowance"""
|
|
# Create excursion and set up invalid allowance configuration
|
|
self.statement.excursion = self.excursion
|
|
self.statement.save()
|
|
|
|
# Add allowance recipient who is not a youth leader for this excursion
|
|
self.statement_no_trans_success.allowance_to.add(self.other_member)
|
|
|
|
# Generate required transactions
|
|
self.statement_no_trans_success.generate_transactions()
|
|
for trans in self.statement_no_trans_success.transaction_set.all():
|
|
trans.ledger = self.ledger
|
|
trans.save()
|
|
|
|
# Check validity obstruction is allowances
|
|
self.assertEqual(self.statement_no_trans_success.validity, Statement.INVALID_ALLOWANCE_TO)
|
|
|
|
url = reverse('admin:finance_statementsubmitted_overview',
|
|
args=(self.statement_no_trans_success.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'confirm': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
# Check the Django messages for the error
|
|
messages = list(get_messages(response.wsgi_request))
|
|
expected_text = str(_("The configured recipients for the allowance don't match the regulations. Please correct this on the excursion."))
|
|
self.assertTrue(any(expected_text in str(msg) for msg in messages))
|
|
|
|
def test_overview_view_reject(self):
|
|
"""Test overview_view reject statement"""
|
|
url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'reject': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
success_text = _("Successfully rejected %(name)s. The requestor can reapply, when needed.") %\
|
|
{'name': str(self.statement)}
|
|
self.assertContains(response, success_text)
|
|
|
|
# Verify statement was rejected
|
|
self.statement.refresh_from_db()
|
|
self.assertFalse(self.statement.submitted)
|
|
|
|
def test_overview_view_generate_transactions_existing(self):
|
|
"""Test overview_view generate transactions with existing transactions"""
|
|
# Ensure there's already a transaction
|
|
self.assertTrue(self.statement.transaction_set.count() > 0)
|
|
|
|
url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'generate_transactions': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
error_text = _("%(name)s already has transactions. Please delete them first, if you want to generate new ones") % {'name': str(self.statement)}
|
|
self.assertContains(response, error_text)
|
|
|
|
def test_overview_view_generate_transactions_success(self):
|
|
"""Test overview_view generate transactions successfully"""
|
|
url = reverse('admin:finance_statementsubmitted_overview',
|
|
args=(self.statement_no_trans_success.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'generate_transactions': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
success_text = _("Successfully generated transactions for %(name)s") %\
|
|
{'name': str(self.statement_no_trans_success)}
|
|
self.assertContains(response, success_text)
|
|
|
|
def test_overview_view_generate_transactions_error(self):
|
|
"""Test overview_view generate transactions with error"""
|
|
url = reverse('admin:finance_statementsubmitted_overview',
|
|
args=(self.statement_no_trans_error.pk,))
|
|
c = self._login('superuser')
|
|
response = c.post(url, follow=True, data={'generate_transactions': ''})
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
messages = list(get_messages(response.wsgi_request))
|
|
expected_text = str(_("Error while generating transactions for %(name)s. Do all bills have a payer and, if this statement is attached to an excursion, was a person selected that receives the subsidies?") %\
|
|
{'name': str(self.statement_no_trans_error)})
|
|
self.assertTrue(any(expected_text in str(msg) for msg in messages))
|
|
|
|
def test_reduce_transactions_view(self):
|
|
url = reverse('admin:finance_statementsubmitted_reduce_transactions',
|
|
args=(self.statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.get(url, data={'redirectTo': reverse('admin:finance_statementsubmitted_changelist')},
|
|
follow=True)
|
|
self.assertContains(response,
|
|
_("Successfully reduced transactions for %(name)s.") %\
|
|
{'name': str(self.statement)})
|
|
|
|
|
|
class StatementConfirmedAdminTestCase(AdminTestCase):
|
|
"""Test cases for StatementConfirmedAdmin"""
|
|
|
|
def setUp(self):
|
|
super().setUp(model=StatementConfirmed, admin=StatementConfirmedAdmin)
|
|
|
|
self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
|
|
self.member = Member.objects.create(
|
|
prename="Test", lastname="User", birth_date=timezone.now().date(),
|
|
email="test@example.com", gender=MALE, user=self.user
|
|
)
|
|
|
|
self.finance_user = User.objects.create_user('finance', 'finance@example.com', 'pass')
|
|
unconfirm_perm = Permission.objects.get(codename='may_manage_confirmed_statements')
|
|
self.finance_user.user_permissions.add(unconfirm_perm)
|
|
|
|
# Create a base statement first
|
|
base_statement = Statement.objects.create(
|
|
short_description='Confirmed Statement',
|
|
explanation='Test explanation',
|
|
status=Statement.CONFIRMED,
|
|
confirmed_by=self.member,
|
|
confirmed_date=timezone.now(),
|
|
night_cost=25
|
|
)
|
|
|
|
# StatementConfirmed is a proxy model, so we can get it from the base statement
|
|
self.statement = StatementConfirmed.objects.get(pk=base_statement.pk)
|
|
|
|
# Create an unconfirmed statement for testing
|
|
self.unconfirmed_statement = Statement.objects.create(
|
|
short_description='Unconfirmed Statement',
|
|
explanation='Test explanation',
|
|
status=Statement.SUBMITTED,
|
|
night_cost=25
|
|
)
|
|
|
|
# Create excursion for testing
|
|
self.excursion = Freizeit.objects.create(
|
|
name='Test Excursion',
|
|
kilometers_traveled=100,
|
|
tour_type=GEMEINSCHAFTS_TOUR,
|
|
tour_approach=MUSKELKRAFT_ANREISE,
|
|
difficulty=1
|
|
)
|
|
|
|
# Create confirmed statement with excursion
|
|
confirmed_with_excursion_base = Statement.objects.create(
|
|
short_description='Confirmed with Excursion',
|
|
explanation='Test explanation',
|
|
status=Statement.CONFIRMED,
|
|
confirmed_by=self.member,
|
|
confirmed_date=timezone.now(),
|
|
excursion=self.excursion,
|
|
night_cost=25
|
|
)
|
|
self.statement_with_excursion = StatementConfirmed.objects.get(pk=confirmed_with_excursion_base.pk)
|
|
|
|
def _add_session_to_request(self, request):
|
|
"""Add session to request"""
|
|
middleware = SessionMiddleware(lambda req: None)
|
|
middleware.process_request(request)
|
|
request.session.save()
|
|
|
|
middleware = MessageMiddleware(lambda req: None)
|
|
middleware.process_request(request)
|
|
request._messages = FallbackStorage(request)
|
|
|
|
def test_has_add_permission(self):
|
|
"""Test that add permission is disabled"""
|
|
request = self.factory.get('/')
|
|
request.user = self.finance_user
|
|
self.assertFalse(self.admin.has_add_permission(request))
|
|
|
|
def test_has_change_permission(self):
|
|
"""Test that change permission is disabled"""
|
|
request = self.factory.get('/')
|
|
request.user = self.finance_user
|
|
self.assertFalse(self.admin.has_change_permission(request))
|
|
|
|
def test_has_delete_permission(self):
|
|
"""Test that delete permission is disabled"""
|
|
request = self.factory.get('/')
|
|
request.user = self.finance_user
|
|
self.assertFalse(self.admin.has_delete_permission(request))
|
|
|
|
def test_unconfirm_view_not_confirmed_statement(self):
|
|
"""Test unconfirm_view with statement that is not confirmed"""
|
|
# Create request for unconfirmed statement
|
|
request = self.factory.get('/')
|
|
request.user = self.finance_user
|
|
self._add_session_to_request(request)
|
|
|
|
# Test with unconfirmed statement (should trigger error path)
|
|
self.assertFalse(self.unconfirmed_statement.confirmed)
|
|
|
|
# Call unconfirm_view - this should go through error path
|
|
response = self.admin.unconfirm_view(request, self.unconfirmed_statement.pk)
|
|
|
|
# Should redirect due to not confirmed error
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
def test_unconfirm_view_post_unconfirm_action(self):
|
|
"""Test unconfirm_view POST request with 'unconfirm' action"""
|
|
# Create POST request with unconfirm action
|
|
request = self.factory.post('/', {'unconfirm': 'true'})
|
|
request.user = self.finance_user
|
|
self._add_session_to_request(request)
|
|
|
|
# Ensure statement is confirmed
|
|
self.assertTrue(self.statement.confirmed)
|
|
self.assertIsNotNone(self.statement.confirmed_by)
|
|
self.assertIsNotNone(self.statement.confirmed_date)
|
|
|
|
# Call unconfirm_view - this should execute the unconfirm action
|
|
response = self.admin.unconfirm_view(request, self.statement.pk)
|
|
|
|
# Should redirect after successful unconfirm
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
# Verify statement was unconfirmed (need to reload from DB)
|
|
self.statement.refresh_from_db()
|
|
self.assertFalse(self.statement.confirmed)
|
|
self.assertIsNone(self.statement.confirmed_date)
|
|
|
|
def test_unconfirm_view_get_render_template(self):
|
|
"""Test unconfirm_view GET request rendering template"""
|
|
# Create GET request (no POST data)
|
|
request = self.factory.get('/')
|
|
request.user = self.finance_user
|
|
self._add_session_to_request(request)
|
|
|
|
# Ensure statement is confirmed
|
|
self.assertTrue(self.statement.confirmed)
|
|
|
|
# Call unconfirm_view
|
|
response = self.admin.unconfirm_view(request, self.statement.pk)
|
|
|
|
# Should render template (status 200)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# Check response content contains expected template elements
|
|
self.assertIn(str(_('Unconfirm statement')).encode('utf-8'), response.content)
|
|
self.assertIn(self.statement.short_description.encode(), response.content)
|
|
|
|
def test_statement_summary_view_insufficient_permission(self):
|
|
url = reverse('admin:finance_statementconfirmed_summary',
|
|
args=(self.statement_with_excursion.pk,))
|
|
c = self._login('standard')
|
|
response = c.get(url, follow=True)
|
|
self.assertEqual(response.status_code, HTTPStatus.FORBIDDEN)
|
|
|
|
def test_statement_summary_view_unconfirmed(self):
|
|
url = reverse('admin:finance_statementconfirmed_summary',
|
|
args=(self.unconfirmed_statement.pk,))
|
|
c = self._login('superuser')
|
|
response = c.get(url, follow=True)
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
self.assertContains(response, _('Statement not found.'))
|
|
|
|
def test_statement_summary_view_confirmed_with_excursion(self):
|
|
"""Test statement_summary_view when statement is confirmed with excursion"""
|
|
url = reverse('admin:finance_statementconfirmed_summary',
|
|
args=(self.statement_with_excursion.pk,))
|
|
c = self._login('superuser')
|
|
response = c.get(url, follow=True)
|
|
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
self.assertEqual(response.headers['Content-Type'], 'application/pdf')
|
|
|
|
|
|
class TransactionAdminTestCase(TestCase):
|
|
"""Test cases for TransactionAdmin"""
|
|
|
|
def setUp(self):
|
|
self.site = AdminSite()
|
|
self.factory = RequestFactory()
|
|
self.admin = TransactionAdmin(Transaction, self.site)
|
|
|
|
self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
|
|
self.member = Member.objects.create(
|
|
prename="Test", lastname="User", birth_date=timezone.now().date(),
|
|
email="test@example.com", gender=MALE, user=self.user
|
|
)
|
|
|
|
self.ledger = Ledger.objects.create(name='Test Ledger')
|
|
self.statement = Statement.objects.create(
|
|
short_description='Test Statement',
|
|
explanation='Test explanation'
|
|
)
|
|
|
|
self.transaction = Transaction.objects.create(
|
|
member=self.member,
|
|
ledger=self.ledger,
|
|
amount=100,
|
|
reference='Test transaction',
|
|
statement=self.statement
|
|
)
|
|
|
|
def test_has_add_permission(self):
|
|
"""Test that add permission is disabled"""
|
|
request = self.factory.get('/')
|
|
request.user = self.user
|
|
self.assertFalse(self.admin.has_add_permission(request))
|
|
|
|
def test_has_change_permission(self):
|
|
"""Test that change permission is disabled"""
|
|
request = self.factory.get('/')
|
|
request.user = self.user
|
|
self.assertFalse(self.admin.has_change_permission(request))
|
|
|
|
def test_has_delete_permission(self):
|
|
"""Test that delete permission is disabled"""
|
|
request = self.factory.get('/')
|
|
request.user = self.user
|
|
self.assertFalse(self.admin.has_delete_permission(request))
|
|
|
|
def test_get_readonly_fields_confirmed(self):
|
|
"""Test readonly fields when transaction is confirmed"""
|
|
self.transaction.confirmed = True
|
|
readonly_fields = self.admin.get_readonly_fields(None, self.transaction)
|
|
self.assertEqual(readonly_fields, self.admin.fields)
|
|
|
|
def test_get_readonly_fields_not_confirmed(self):
|
|
"""Test readonly fields when transaction is not confirmed"""
|
|
readonly_fields = self.admin.get_readonly_fields(None, self.transaction)
|
|
self.assertEqual(readonly_fields, ())
|