* adds workflow tests for checkability and recordability for interventions
* fixes/improves code snippets detected by testing
This commit is contained in:
mpeltriaux 2021-11-11 10:37:22 +01:00
parent 34fd78be5e
commit 796990ffbc
7 changed files with 205 additions and 34 deletions

View File

@ -182,7 +182,10 @@ class NewRevocationModalForm(BaseModalForm):
return revocation return revocation
class RunCheckModalForm(BaseModalForm): class CheckModalForm(BaseModalForm):
""" The modal form for running a check on interventions and their compensations
"""
checked_intervention = forms.BooleanField( checked_intervention = forms.BooleanField(
label=_("Checked intervention data"), label=_("Checked intervention data"),
label_suffix="", label_suffix="",

View File

@ -16,7 +16,7 @@
{% fa5_icon 'share-alt' %} {% fa5_icon 'share-alt' %}
</button> </button>
{% if is_zb_member %} {% if is_zb_member %}
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Run check' %}" data-form-url="{% url 'intervention:run-check' obj.id %}"> <button class="btn btn-default btn-modal mr-2" title="{% trans 'Run check' %}" data-form-url="{% url 'intervention:check' obj.id %}">
{% fa5_icon 'star' %} {% fa5_icon 'star' %}
</button> </button>
{% endif %} {% endif %}

View File

@ -30,7 +30,7 @@ class InterventionViewTestCase(BaseViewTestCase):
cls.remove_url = reverse("intervention:remove", args=(cls.intervention.id,)) cls.remove_url = reverse("intervention:remove", args=(cls.intervention.id,))
cls.share_url = reverse("intervention:share", args=(cls.intervention.id, cls.intervention.access_token,)) cls.share_url = reverse("intervention:share", args=(cls.intervention.id, cls.intervention.access_token,))
cls.share_create_url = reverse("intervention:share-create", args=(cls.intervention.id,)) cls.share_create_url = reverse("intervention:share-create", args=(cls.intervention.id,))
cls.run_check_url = reverse("intervention:run-check", args=(cls.intervention.id,)) cls.run_check_url = reverse("intervention:check", args=(cls.intervention.id,))
cls.record_url = reverse("intervention:record", args=(cls.intervention.id,)) cls.record_url = reverse("intervention:record", args=(cls.intervention.id,))
cls.report_url = reverse("intervention:report", args=(cls.intervention.id,)) cls.report_url = reverse("intervention:report", args=(cls.intervention.id,))

View File

@ -7,13 +7,12 @@ Created on: 10.11.21
""" """
import datetime import datetime
from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse from django.urls import reverse
from compensation.models import Payment, EcoAccountDeduction from compensation.models import Payment, EcoAccountDeduction
from intervention.models import Intervention from intervention.models import Intervention
from konova.settings import DEFAULT_GROUP from konova.settings import DEFAULT_GROUP, ETS_GROUP, ZB_GROUP
from konova.tests.test_views import BaseWorkflowTestCase from konova.tests.test_views import BaseWorkflowTestCase
from user.models import UserActionLogEntry, UserAction from user.models import UserActionLogEntry, UserAction
@ -26,11 +25,19 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
super().setUpTestData() super().setUpTestData()
cls.new_url = reverse("intervention:new", args=())
# Add user to the default group -> give default permissions # Give the user shared access to the dummy intervention
default_group = Group.objects.get(name=DEFAULT_GROUP) cls.intervention.users.add(cls.superuser)
cls.superuser.groups.set([default_group])
def setUp(self) -> None:
""" Setup data before each test run
Returns:
"""
# Set the default group as only group for the user
default_group = self.groups.get(name=DEFAULT_GROUP)
self.superuser.groups.set([default_group])
def test_new(self): def test_new(self):
""" """
@ -45,6 +52,8 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
# Define the intervention identifier for easier handling on the next lines # Define the intervention identifier for easier handling on the next lines
test_id = "Test_IDENTIFIER" test_id = "Test_IDENTIFIER"
new_url = reverse("intervention:new", args=())
# Expect the new intervention does not exist yet # Expect the new intervention does not exist yet
obj_exists = Intervention.objects.filter( obj_exists = Intervention.objects.filter(
identifier=test_id identifier=test_id
@ -58,7 +67,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
"geometry": "", "geometry": "",
} }
response = self.client_user.post( response = self.client_user.post(
self.new_url, new_url,
post_data post_data
) )
@ -80,6 +89,118 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
self.assertIn(self.superuser, obj.users.all()) self.assertIn(self.superuser, obj.users.all())
self.assertEqual(1, obj.users.count()) self.assertEqual(1, obj.users.count())
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())
def subtest_add_payment(self): def subtest_add_payment(self):
""" Subroutine for 'normal' payment tests """ Subroutine for 'normal' payment tests
@ -89,9 +210,9 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
Returns: Returns:
""" """
## Attention: Despite the fact, this url refers to a compensation app route, we test it here for the interventions. # 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. # 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. # 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,)) new_payment_url = reverse("compensation:pay-new", args=(self.intervention.id,))
# Make sure there are no payments on the intervention, yet # Make sure there are no payments on the intervention, yet

View File

@ -8,7 +8,7 @@ Created on: 30.11.20
from django.urls import path from django.urls import path
from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \ from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \
create_share_view, remove_revocation_view, new_revocation_view, run_check_view, log_view, new_deduction_view, \ create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \
record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view
app_name = "intervention" app_name = "intervention"
@ -22,7 +22,7 @@ urlpatterns = [
path('<id>/remove', remove_view, name='remove'), path('<id>/remove', remove_view, name='remove'),
path('<id>/share/<token>', share_view, name='share'), path('<id>/share/<token>', share_view, name='share'),
path('<id>/share', create_share_view, name='share-create'), path('<id>/share', create_share_view, name='share-create'),
path('<id>/check', run_check_view, name='run-check'), path('<id>/check', check_view, name='check'),
path('<id>/record', record_view, name='record'), path('<id>/record', record_view, name='record'),
path('<id>/report', report_view, name='report'), path('<id>/report', report_view, name='report'),

View File

@ -5,7 +5,7 @@ from django.shortcuts import render
from intervention.forms.forms import NewInterventionForm, EditInterventionForm from intervention.forms.forms import NewInterventionForm, EditInterventionForm
from intervention.forms.modalForms import ShareInterventionModalForm, NewRevocationModalForm, \ from intervention.forms.modalForms import ShareInterventionModalForm, NewRevocationModalForm, \
RunCheckModalForm, NewDeductionModalForm CheckModalForm, NewDeductionModalForm
from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument
from intervention.tables import InterventionTable from intervention.tables import InterventionTable
from konova.contexts import BaseContext from konova.contexts import BaseContext
@ -413,7 +413,7 @@ def create_share_view(request: HttpRequest, id: str):
@login_required @login_required
@registration_office_group_required @registration_office_group_required
@shared_access_required(Intervention, "id") @shared_access_required(Intervention, "id")
def run_check_view(request: HttpRequest, id: str): def check_view(request: HttpRequest, id: str):
""" Renders check form for an intervention """ Renders check form for an intervention
Args: Args:
@ -424,7 +424,7 @@ def run_check_view(request: HttpRequest, id: str):
""" """
intervention = get_object_or_404(Intervention, id=id) intervention = get_object_or_404(Intervention, id=id)
form = RunCheckModalForm(request.POST or None, instance=intervention, user=request.user) form = CheckModalForm(request.POST or None, instance=intervention, user=request.user)
return form.process_request( return form.process_request(
request, request,
msg_success=_("Check performed"), msg_success=_("Check performed"),

View File

@ -5,13 +5,15 @@ Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 26.10.21 Created on: 26.10.21
""" """
from abc import abstractmethod import datetime
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.contrib.gis.geos import MultiPolygon, Polygon
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase, Client from django.test import TestCase, Client
from django.urls import reverse from django.urls import reverse
from codelist.models import KonovaCode
from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount
from intervention.models import LegalData, ResponsibilityData, Intervention from intervention.models import LegalData, ResponsibilityData, Intervention
from konova.management.commands.setup_data import GROUPS_DATA from konova.management.commands.setup_data import GROUPS_DATA
@ -32,17 +34,22 @@ class BaseTestCase(TestCase):
eco_account = None eco_account = None
comp_state = None comp_state = None
comp_action = None comp_action = None
codes = None
superuser_pw = "root" superuser_pw = "root"
user_pw = "root" user_pw = "root"
class Meta:
abstract = True
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.create_users() cls.create_users()
cls.create_groups() cls.create_groups()
cls.create_dummy_intervention() cls.intervention = cls.create_dummy_intervention()
cls.create_dummy_compensation() cls.compensation = cls.create_dummy_compensation()
cls.create_dummy_eco_account() cls.eco_account = cls.create_dummy_eco_account()
cls.codes = cls.create_dummy_codes()
@classmethod @classmethod
def create_users(cls): def create_users(cls):
@ -69,9 +76,6 @@ class BaseTestCase(TestCase):
) )
cls.groups = Group.objects.all() cls.groups = Group.objects.all()
class Meta:
abstract = True
@classmethod @classmethod
def create_dummy_intervention(cls): def create_dummy_intervention(cls):
""" Creates an intervention which can be used for tests """ Creates an intervention which can be used for tests
@ -91,7 +95,7 @@ class BaseTestCase(TestCase):
responsibility_data = ResponsibilityData.objects.create() responsibility_data = ResponsibilityData.objects.create()
geometry = Geometry.objects.create() geometry = Geometry.objects.create()
# Finally create main object, holding the other objects # Finally create main object, holding the other objects
cls.intervention = Intervention.objects.create( intervention = Intervention.objects.create(
identifier="TEST", identifier="TEST",
title="Test_title", title="Test_title",
responsible=responsibility_data, responsible=responsibility_data,
@ -100,8 +104,8 @@ class BaseTestCase(TestCase):
geometry=geometry, geometry=geometry,
comment="Test", comment="Test",
) )
cls.intervention.generate_access_token(make_unique=True) intervention.generate_access_token(make_unique=True)
return cls.intervention return intervention
@classmethod @classmethod
def create_dummy_compensation(cls): def create_dummy_compensation(cls):
@ -111,7 +115,7 @@ class BaseTestCase(TestCase):
""" """
if cls.intervention is None: if cls.intervention is None:
cls.create_dummy_intervention() cls.intervention = cls.create_dummy_intervention()
# Create dummy data # Create dummy data
# Create log entry # Create log entry
action = UserActionLogEntry.objects.create( action = UserActionLogEntry.objects.create(
@ -120,7 +124,7 @@ class BaseTestCase(TestCase):
) )
geometry = Geometry.objects.create() geometry = Geometry.objects.create()
# Finally create main object, holding the other objects # Finally create main object, holding the other objects
cls.compensation = Compensation.objects.create( compensation = Compensation.objects.create(
identifier="TEST", identifier="TEST",
title="Test_title", title="Test_title",
intervention=cls.intervention, intervention=cls.intervention,
@ -128,8 +132,7 @@ class BaseTestCase(TestCase):
geometry=geometry, geometry=geometry,
comment="Test", comment="Test",
) )
cls.intervention.generate_access_token(make_unique=True) return compensation
return cls.compensation
@classmethod @classmethod
def create_dummy_eco_account(cls): def create_dummy_eco_account(cls):
@ -149,7 +152,7 @@ class BaseTestCase(TestCase):
lega_data = LegalData.objects.create() lega_data = LegalData.objects.create()
responsible_data = ResponsibilityData.objects.create() responsible_data = ResponsibilityData.objects.create()
# Finally create main object, holding the other objects # Finally create main object, holding the other objects
cls.eco_account = EcoAccount.objects.create( eco_account = EcoAccount.objects.create(
identifier="TEST", identifier="TEST",
title="Test_title", title="Test_title",
legal=lega_data, legal=lega_data,
@ -158,7 +161,7 @@ class BaseTestCase(TestCase):
geometry=geometry, geometry=geometry,
comment="Test", comment="Test",
) )
return cls.eco_account return eco_account
@classmethod @classmethod
def create_dummy_states(cls): def create_dummy_states(cls):
@ -185,6 +188,47 @@ class BaseTestCase(TestCase):
) )
return cls.comp_action return cls.comp_action
@classmethod
def create_dummy_codes(cls):
""" Creates some dummy KonovaCodes which can be used for testing
Returns:
"""
codes = KonovaCode.objects.bulk_create([
KonovaCode(id=1, is_selectable=True, long_name="Test1"),
KonovaCode(id=2, is_selectable=True, long_name="Test2"),
KonovaCode(id=3, is_selectable=True, long_name="Test3"),
KonovaCode(id=4, is_selectable=True, long_name="Test4"),
])
return codes
@staticmethod
def fill_out_intervention(intervention: Intervention) -> Intervention:
""" Adds all required (dummy) data to an intervention
Args:
intervention (Intervention): The intervention which shall be filled out
Returns:
intervention (Intervention): The modified intervention
"""
intervention.responsible.registration_office = KonovaCode.objects.get(id=1)
intervention.responsible.conservation_office = KonovaCode.objects.get(id=2)
intervention.responsible.registration_file_number = "test"
intervention.responsible.conservation_file_number = "test"
intervention.responsible.handler = "handler"
intervention.responsible.save()
intervention.legal.registration_date = datetime.date.fromisoformat("1970-01-01")
intervention.legal.binding_date = datetime.date.fromisoformat("1970-01-01")
intervention.legal.process_type = KonovaCode.objects.get(id=3)
intervention.legal.save()
intervention.legal.laws.set([KonovaCode.objects.get(id=(4))])
intervention.geometry.geom = MultiPolygon(Polygon.from_bbox([-4.526367, 18.354526, -1.801758, 20.591652]))
intervention.geometry.save()
intervention.save()
return intervention
class BaseViewTestCase(BaseTestCase): class BaseViewTestCase(BaseTestCase):
""" Wraps basic test functionality, reusable for every specialized ViewTestCase """ Wraps basic test functionality, reusable for every specialized ViewTestCase
@ -348,6 +392,9 @@ class BaseWorkflowTestCase(BaseTestCase):
client_user = None client_user = None
client_anon = None client_anon = None
class Meta:
abstract = True
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
super().setUpTestData() super().setUpTestData()