""" Author: Michel Peltriaux Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany Contact: michel.peltriaux@sgdnord.rlp.de Created on: 10.11.21 """ import datetime from django.contrib.auth.models import Group from django.core.exceptions import ObjectDoesNotExist from django.urls import reverse from compensation.models import Payment, EcoAccountDeduction from intervention.models import Intervention from konova.settings import DEFAULT_GROUP from konova.tests.test_views import BaseWorkflowTestCase from user.models import UserActionLogEntry, UserAction class InterventionWorkflowTestCase(BaseWorkflowTestCase): """ This test case adds workflow tests """ @classmethod def setUpTestData(cls): super().setUpTestData() cls.new_url = reverse("intervention:new", args=()) # Add user to the default group -> give default permissions default_group = Group.objects.get(name=DEFAULT_GROUP) cls.superuser.groups.set([default_group]) def test_new(self): """ Checks a 'normal' case of creating a new intervention. We expect the user to be redirected as expected right away to the detail page of the new intervention. We expect the user to be directly added to the shared user of the intervention We expect that a minimum of data (identifier, title, (empty) geometry) can be used to create an intervention Returns: """ # Define the intervention identifier for easier handling on the next lines test_id = "Test_IDENTIFIER" # Expect the new intervention does not exist yet obj_exists = Intervention.objects.filter( identifier=test_id ).exists() self.assertFalse(obj_exists) # User creates a new intervention with bare minimum content, using the proper url and post data post_data = { "identifier": test_id, "title": "Test_TITLE", "geometry": "", } response = self.client_user.post( self.new_url, post_data ) # Now expect the new intervention to exist in the db try: obj = Intervention.objects.get( identifier=test_id ) self.assertEqual(obj.identifier, test_id) except ObjectDoesNotExist: # Fail if there is no such object self.fail() expected_redirect = reverse("intervention:detail", args=(obj.id,)) # Expect redirect to the detail view of the new intervention self.assertRedirects(response, expected_redirect) # Expect user to be first and only user with shared access self.assertIn(self.superuser, obj.users.all()) self.assertEqual(1, obj.users.count()) def subtest_add_payment(self): """ Subroutine for 'normal' payment tests Checks a 'normal' case of adding a payment. We expect a new payment to be addable to an existing intervention Returns: """ ## Attention: Despite the fact, this url refers to a compensation app route, we test it here for the interventions. ## Reason: A payment is some kind of compensation for an intervention. Therefore it lives inside the compensation app. ## BUT: Payments are added on the intervention detail page. Therefore it's part of a regular intervention workflow. new_payment_url = reverse("compensation:pay-new", args=(self.intervention.id,)) # Make sure there are no payments on the intervention, yet self.assertEqual(0, self.intervention.payments.count()) # Create form data to be sent to the url test_amount = 10.00 test_due = "2021-01-01" test_comment = "test_comment" post_data = { "amount": test_amount, "due": test_due, "comment": test_comment } self.client_user.post( new_payment_url, post_data, ) # We do not test for any redirects in here, since the new payment url is realized using a modal, which does not # perform any direct redirects but instead reloads the page after finisihing. # Make sure there is a new payment on the intervention now self.assertEqual(1, self.intervention.payments.count()) # Make sure the payment contains our data payment = self.intervention.payments.all()[0] self.assertEqual(payment.amount, test_amount) self.assertEqual(payment.due_on, datetime.date.fromisoformat(test_due)) self.assertEqual(payment.comment, test_comment) return payment def subtest_delete_payment(self, payment: Payment): """ Subroutine for 'normal' payment tests Checks a 'normal' case of adding a payment. We expect a payment to be deletable to an existing intervention Returns: """ # Create removing url for the payment remove_url = reverse("compensation:pay-remove", args=(payment.id,)) post_data = { "confirm": True, } self.client_user.post( remove_url, post_data ) # Expect the payment to be gone from the db and therefore from the intervention as well self.assert_object_is_deleted(payment) # Now make sure the intervention has no payments anymore self.assertEqual(0, self.intervention.payments.count()) def test_payments(self): """ Checks a 'normal' case of adding a payment. We expect a new payment to be addable to an existing intervention We expect a payment to be deletable from an existing intervention Returns: """ # Create new payment for the default intervention payment = self.subtest_add_payment() # Now remove the payment again self.subtest_delete_payment(payment) def subtest_add_deduction_fail_positive(self, new_url: str, post_data: dict, test_surface: float): """ Holds tests for postivie fails of new deduction creation Reasons for failing are: * EcoAccount does not provide enough 'deductable_surface' * EcoAccount is not recorded (not "approved"), yet * EcoAccount is not shared with performing user Args: new_url (str): The url to send the post data to post_data (dict): The form post data to be sent Returns: """ # Before running fail positive tests, we need to have an account in a (normally) fine working state self.assertIsNotNone(self.eco_account.recorded) # -> is recorded self.assertGreater(self.eco_account.deductable_surface, test_surface) # -> has more deductable surface than we need self.assertIn(self.superuser, self.eco_account.users.all()) # -> is shared with the performing user # Count the number of already existing deductions in total and for the account for later comparison num_deductions = self.eco_account.deductions.count() num_deductions_total = EcoAccountDeduction.objects.count() # First test that a deduction can not be created, if the account does not provide # enough surface for the deduction. So we modify the deductable surface of the account self.eco_account.deductable_surface = 0 self.eco_account.save() # Now perform the (expected) failing request self.client_user.post(new_url, post_data) # Expect no changes at all, since the deduction should not have been created self.assertEqual(num_deductions, self.eco_account.deductions.count()) self.assertEqual(num_deductions_total, EcoAccountDeduction.objects.count()) # Now restore the deductable surface to a valid size back again but remove the user from the shared list self.eco_account.deductable_surface = test_surface + 100.00 self.eco_account.users.set([]) self.eco_account.save() # Now perform the (expected) failing request (again) self.client_user.post(new_url, post_data) # Expect no changes at all, since the account is not shared self.assertEqual(num_deductions, self.eco_account.deductions.count()) self.assertEqual(num_deductions_total, EcoAccountDeduction.objects.count()) # Restore the sharing but remove the recording state self.eco_account.users.set([self.superuser]) self.eco_account.recorded.delete() self.eco_account.refresh_from_db() self.eco_account.save() # Now perform the (expected) failing request (again) self.client_user.post(new_url, post_data) # Expect no changes at all, since the account is no shared with the user, yet self.assertEqual(num_deductions, self.eco_account.deductions.count()) self.assertEqual(num_deductions_total, EcoAccountDeduction.objects.count()) def subtest_add_deduction_normal(self, new_url: str, post_data: dict, test_surface: float): """ Holds tests on working ("normal") deduction creation Args: new_url (str): The url to send the post data to post_data (dict): The form post data to be sent test_surface (float): The expected surface of the deduction Returns: """ # Prepare the account for a working situation (enough deductable surface, recorded and shared) self.eco_account.deductable_surface = 10000.00 if self.eco_account.recorded is None: rec_action = UserActionLogEntry.objects.create( user=self.superuser, action=UserAction.RECORDED ) self.eco_account.recorded = rec_action self.eco_account.users.set([self.superuser]) self.eco_account.save() # Run the request self.client_user.post(new_url, post_data) # Expect the deduction to be created, since all constraints are fulfilled self.assertEqual(1, self.eco_account.deductions.count()) self.assertEqual(1, EcoAccountDeduction.objects.count()) # Make sure the deduction contains the expected data deduction = EcoAccountDeduction.objects.first() self.assertEqual(deduction.surface, test_surface) self.assertEqual(deduction.intervention, self.intervention) self.assertEqual(deduction.account, self.eco_account) # Return deduction for further usage in tests return deduction def subtest_add_deduction(self): """ Holds test for adding a new deduction Contains tests for * positive fails (as expected) * normal cases Returns: """ # Create the url for creating a new deduction new_url = reverse("compensation:acc-new-deduction", args=(self.eco_account.id,)) # Prepare the form data test_surface = 100.00 post_data = { "surface": test_surface, "account": self.eco_account.id, "intervention": self.intervention.id, } # Run some tests for regular, working cases deduction = self.subtest_add_deduction_normal(new_url, post_data, test_surface) # Run some tests where we expect the creation of a deduction to fail (as expected) self.subtest_add_deduction_fail_positive(new_url, post_data, test_surface) # Return deduction for further usage in tests return deduction def subtest_delete_deduction(self, deduction: EcoAccountDeduction): """ Holds test for deleting a deduction Returns: """ # Prepare url for deleting of this deduction delete_url = reverse("compensation:acc-remove-deduction", args=(self.eco_account.id, deduction.id,)) post_data = { "confirm": True } # Save number of current deductions for later comparison num_deductions = self.eco_account.deductions.count() num_deductions_total = EcoAccountDeduction.objects.count() # Run request self.client_user.post(delete_url, post_data) # Expect the deduction to be gone from the db and relations self.assertEqual(num_deductions - 1, self.eco_account.deductions.count()) self.assertEqual(num_deductions_total - 1, EcoAccountDeduction.objects.count()) # Expect the deduction to be totally gone self.assert_object_is_deleted(deduction) def test_deduction(self): """ Checks a 'normal case of adding a deduction. We expect a new deduction to be addable to an existing intervention We expect a deduction to be deletable Returns: """ # Create a new deduction for the default intervention deduction = self.subtest_add_deduction() # Now remove the deduction again self.subtest_delete_deduction(deduction)