2021-11-10 14:08:16 +01:00
|
|
|
"""
|
|
|
|
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.core.exceptions import ObjectDoesNotExist
|
|
|
|
from django.urls import reverse
|
|
|
|
|
2021-11-10 15:36:18 +01:00
|
|
|
from compensation.models import Payment, EcoAccountDeduction
|
2021-11-10 14:08:16 +01:00
|
|
|
from intervention.models import Intervention
|
2021-11-12 16:42:40 +01:00
|
|
|
from konova.settings import ETS_GROUP, ZB_GROUP
|
2021-11-10 14:08:16 +01:00
|
|
|
from konova.tests.test_views import BaseWorkflowTestCase
|
2021-11-10 15:36:18 +01:00
|
|
|
from user.models import UserActionLogEntry, UserAction
|
2021-11-10 14:08:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
|
|
|
""" This test case adds workflow tests
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def setUpTestData(cls):
|
|
|
|
super().setUpTestData()
|
|
|
|
|
2021-11-12 16:42:40 +01:00
|
|
|
def setUp(self) -> None:
|
|
|
|
super().setUp()
|
|
|
|
# Recreate a new (bare minimum) intervention before each test
|
|
|
|
self.intervention = self.create_dummy_intervention()
|
|
|
|
self.intervention.share_with(self.superuser)
|
2021-11-11 10:37:22 +01:00
|
|
|
|
2021-11-10 15:36:18 +01:00
|
|
|
def test_new(self):
|
2021-11-10 14:08:16 +01:00
|
|
|
"""
|
|
|
|
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
|
2021-11-12 14:54:33 +01:00
|
|
|
test_id = self.create_dummy_string()
|
|
|
|
test_title = self.create_dummy_string()
|
|
|
|
test_geom = self.create_dummy_geometry()
|
2021-11-10 14:08:16 +01:00
|
|
|
|
2021-11-11 10:37:22 +01:00
|
|
|
new_url = reverse("intervention:new", args=())
|
|
|
|
|
2021-11-10 14:08:16 +01:00
|
|
|
# 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,
|
2021-11-12 14:54:33 +01:00
|
|
|
"title": test_title,
|
|
|
|
"geom": test_geom.geojson,
|
2021-11-10 14:08:16 +01:00
|
|
|
}
|
|
|
|
response = self.client_user.post(
|
2021-11-11 10:37:22 +01:00
|
|
|
new_url,
|
2021-11-10 14:08:16 +01:00
|
|
|
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)
|
2021-11-12 14:54:33 +01:00
|
|
|
self.assertEqual(obj.title, test_title)
|
|
|
|
self.assert_equal_geometries(obj.geometry.geom, test_geom)
|
2021-11-10 14:08:16 +01:00
|
|
|
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())
|
|
|
|
|
2021-11-11 10:37:22 +01:00
|
|
|
def test_checkability(self):
|
|
|
|
""" Tests that the intervention can only be checked if all required data has been added
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
check_url = reverse("intervention:check", args=(self.intervention.id,))
|
|
|
|
post_data = {
|
|
|
|
"checked_intervention": True,
|
|
|
|
"checked_comps": True,
|
|
|
|
}
|
|
|
|
|
|
|
|
# First of all, the intervention should not be checked, yet
|
|
|
|
if self.intervention.checked:
|
|
|
|
self.intervention.checked.delete()
|
|
|
|
self.intervention.refresh_from_db()
|
|
|
|
|
|
|
|
# Make sure the dummy compensation is currently not linked to the intervention,
|
|
|
|
# since the system would check on it's quality as well (and it would fail)
|
|
|
|
self.intervention.compensations.set([])
|
|
|
|
|
|
|
|
# Run request with an incomplete intervention and missing user privileges --> expect to fail
|
|
|
|
self.client_user.post(check_url, post_data)
|
|
|
|
|
|
|
|
# We expect that the intervention is still not checked now
|
|
|
|
self.intervention.refresh_from_db()
|
|
|
|
self.assertIsNone(self.intervention.checked)
|
|
|
|
|
|
|
|
# Now give the user the required privileges by adding to the registration office group
|
|
|
|
group = self.groups.get(name=ZB_GROUP)
|
|
|
|
self.superuser.groups.add(group)
|
|
|
|
|
|
|
|
# Now fill in the missing data, so the intervention is 'valid' for checking
|
|
|
|
self.intervention = self.fill_out_intervention(self.intervention)
|
|
|
|
|
|
|
|
# Then add a dummy payment, so we pass the quality check (Checks whether any kind of valid compensation exists)
|
|
|
|
payment = Payment.objects.create(amount=10.00, due_on=None, comment="No due date because test")
|
|
|
|
self.intervention.payments.add(payment)
|
|
|
|
|
|
|
|
# Run request again
|
|
|
|
self.client_user.post(check_url, post_data)
|
|
|
|
|
|
|
|
# Update intervention from db
|
|
|
|
self.intervention.refresh_from_db()
|
|
|
|
|
|
|
|
# We expect the intervention to be checked now and contains the proper data
|
|
|
|
# Attention: We check the timestamp only on the date, not the time, since the microseconds delay would result
|
|
|
|
# in an unwanted assertion error
|
|
|
|
checked = self.intervention.checked
|
|
|
|
self.assertIsNotNone(checked)
|
|
|
|
self.assertEqual(self.superuser, checked.user)
|
|
|
|
self.assertEqual(datetime.date.today(), checked.timestamp.date())
|
|
|
|
self.assertEqual(UserAction.CHECKED, checked.action)
|
|
|
|
|
|
|
|
# Expect the user action now to live in the log
|
|
|
|
self.assertIn(checked, self.intervention.log.all())
|
|
|
|
|
|
|
|
def test_recordability(self):
|
|
|
|
""" Tests that the intervention can only be recorded if all required data has been added
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
record_url = reverse("intervention:record", args=(self.intervention.id,))
|
|
|
|
post_data = {
|
|
|
|
"confirm": True,
|
|
|
|
}
|
|
|
|
|
|
|
|
# Make sure the dummy compensation is currently not linked to the intervention,
|
|
|
|
# since we would check on it's quality as well then
|
|
|
|
self.intervention.compensations.set([])
|
|
|
|
|
|
|
|
# First of all, the intervention should not be recorded, yet
|
|
|
|
if self.intervention.recorded:
|
|
|
|
self.intervention.recorded.delete()
|
|
|
|
self.intervention.refresh_from_db()
|
|
|
|
|
|
|
|
# Run request with an incomplete intervention and missing user privileges --> expect to fail
|
|
|
|
self.client_user.post(record_url, post_data)
|
|
|
|
|
|
|
|
# We expect that the intervention is still not recorded now
|
|
|
|
self.intervention.refresh_from_db()
|
|
|
|
self.assertIsNone(self.intervention.recorded)
|
|
|
|
|
|
|
|
# Now give the user the required privileges by adding to the ETS group
|
|
|
|
group = self.groups.get(name=ETS_GROUP)
|
|
|
|
self.superuser.groups.add(group)
|
|
|
|
|
|
|
|
# Now fill in the missing data, so the intervention is 'valid' for recording
|
|
|
|
self.intervention = self.fill_out_intervention(self.intervention)
|
|
|
|
|
|
|
|
# Then add a dummy payment, so we pass the quality check (Checks whether any kind of valid compensation exists)
|
|
|
|
payment = Payment.objects.create(amount=10.00, due_on=None, comment="No due date because test")
|
|
|
|
self.intervention.payments.add(payment)
|
|
|
|
|
|
|
|
# Run request again
|
|
|
|
self.client_user.post(record_url, post_data)
|
|
|
|
|
|
|
|
# Update intervention from db
|
|
|
|
self.intervention.refresh_from_db()
|
|
|
|
|
|
|
|
# We expect the intervention to be recorded now and contains the proper data
|
|
|
|
# Attention: We check the timestamp only on the date, not the time, since the microseconds delay would result
|
|
|
|
# in an unwanted assertion error
|
|
|
|
self.assertIsNotNone(self.intervention.recorded)
|
|
|
|
self.assertEqual(self.superuser, self.intervention.recorded.user)
|
|
|
|
self.assertEqual(datetime.date.today(), self.intervention.recorded.timestamp.date())
|
|
|
|
self.assertEqual(UserAction.RECORDED, self.intervention.recorded.action)
|
|
|
|
|
|
|
|
# Expect the user action now to live in the log
|
|
|
|
self.assertIn(self.intervention.recorded, self.intervention.log.all())
|
|
|
|
|
2021-11-10 14:08:16 +01:00
|
|
|
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:
|
|
|
|
|
|
|
|
"""
|
2021-11-11 10:37:22 +01:00
|
|
|
# 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.
|
2021-11-10 14:08:16 +01:00
|
|
|
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"
|
2021-11-12 14:54:33 +01:00
|
|
|
test_comment = self.create_dummy_string()
|
2021-11-10 14:08:16 +01:00
|
|
|
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
|
2021-11-10 15:36:18 +01:00
|
|
|
self.assert_object_is_deleted(payment)
|
|
|
|
|
2021-11-10 14:08:16 +01:00
|
|
|
# Now make sure the intervention has no payments anymore
|
|
|
|
self.assertEqual(0, self.intervention.payments.count())
|
|
|
|
|
2021-11-10 15:36:18 +01:00
|
|
|
def test_payments(self):
|
2021-11-10 14:08:16 +01:00
|
|
|
"""
|
|
|
|
Checks a 'normal' case of adding a payment.
|
|
|
|
We expect a new payment to be addable to an existing intervention
|
2021-11-10 15:36:18 +01:00
|
|
|
We expect a payment to be deletable from an existing intervention
|
2021-11-10 14:08:16 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
# Create new payment for the default intervention
|
|
|
|
payment = self.subtest_add_payment()
|
|
|
|
|
|
|
|
# Now remove the payment again
|
|
|
|
self.subtest_delete_payment(payment)
|
2021-11-10 15:36:18 +01:00
|
|
|
|
|
|
|
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
|
2021-11-11 15:09:03 +01:00
|
|
|
self.eco_account.share_with_list([])
|
2021-11-10 15:36:18 +01:00
|
|
|
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
|
2021-11-11 15:09:03 +01:00
|
|
|
self.eco_account.share_with_list([self.superuser])
|
2021-11-10 15:36:18 +01:00
|
|
|
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:
|
2021-11-16 13:15:15 +01:00
|
|
|
rec_action = UserActionLogEntry.get_recorded_action(self.superuser)
|
2021-11-10 15:36:18 +01:00
|
|
|
self.eco_account.recorded = rec_action
|
2021-11-11 15:09:03 +01:00
|
|
|
self.eco_account.share_with_list([self.superuser])
|
2021-11-10 15:36:18 +01:00
|
|
|
self.eco_account.save()
|
2022-01-28 16:21:23 +01:00
|
|
|
num_all_deducs = EcoAccountDeduction.objects.count()
|
2021-11-10 15:36:18 +01:00
|
|
|
|
|
|
|
# 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())
|
2022-01-28 16:21:23 +01:00
|
|
|
self.assertEqual(num_all_deducs + 1, EcoAccountDeduction.objects.count())
|
2021-11-10 15:36:18 +01:00
|
|
|
|
|
|
|
# Make sure the deduction contains the expected data
|
2022-01-28 16:21:23 +01:00
|
|
|
deduction = EcoAccountDeduction.objects.get(
|
|
|
|
account=self.eco_account,
|
|
|
|
intervention=self.intervention
|
|
|
|
)
|
2021-11-10 15:36:18 +01:00
|
|
|
self.assertEqual(deduction.surface, test_surface)
|
|
|
|
|
|
|
|
# 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)
|