wbcompliance 2.2.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wbcompliance/__init__.py +1 -0
- wbcompliance/admin/__init__.py +16 -0
- wbcompliance/admin/compliance_form.py +56 -0
- wbcompliance/admin/compliance_task.py +135 -0
- wbcompliance/admin/compliance_type.py +8 -0
- wbcompliance/admin/risk_management/__init__.py +3 -0
- wbcompliance/admin/risk_management/checks.py +7 -0
- wbcompliance/admin/risk_management/incidents.py +50 -0
- wbcompliance/admin/risk_management/rules.py +63 -0
- wbcompliance/admin/utils.py +46 -0
- wbcompliance/apps.py +14 -0
- wbcompliance/factories/__init__.py +21 -0
- wbcompliance/factories/compliance.py +246 -0
- wbcompliance/factories/risk_management/__init__.py +12 -0
- wbcompliance/factories/risk_management/backends.py +42 -0
- wbcompliance/factories/risk_management/checks.py +12 -0
- wbcompliance/factories/risk_management/incidents.py +84 -0
- wbcompliance/factories/risk_management/rules.py +100 -0
- wbcompliance/filters/__init__.py +2 -0
- wbcompliance/filters/compliances.py +189 -0
- wbcompliance/filters/risk_management/__init__.py +3 -0
- wbcompliance/filters/risk_management/checks.py +22 -0
- wbcompliance/filters/risk_management/incidents.py +113 -0
- wbcompliance/filters/risk_management/rules.py +110 -0
- wbcompliance/filters/risk_management/tables.py +112 -0
- wbcompliance/filters/risk_management/utils.py +3 -0
- wbcompliance/management/__init__.py +10 -0
- wbcompliance/migrations/0001_initial_squashed_squashed_0010_alter_checkedobjectincidentrelationship_resolved_by_and_more.py +1744 -0
- wbcompliance/migrations/0011_alter_riskrule_parameters.py +21 -0
- wbcompliance/migrations/0012_alter_compliancetype_options.py +20 -0
- wbcompliance/migrations/0013_alter_riskrule_unique_together.py +16 -0
- wbcompliance/migrations/0014_alter_reviewcompliancetask_year.py +27 -0
- wbcompliance/migrations/0015_auto_20240103_0957.py +43 -0
- wbcompliance/migrations/0016_checkedobjectincidentrelationship_report_details_and_more.py +37 -0
- wbcompliance/migrations/0017_alter_rulebackend_incident_report_template.py +20 -0
- wbcompliance/migrations/0018_alter_rulecheckedobjectrelationship_unique_together.py +39 -0
- wbcompliance/migrations/0019_rulegroup_riskrule_activation_date_and_more.py +60 -0
- wbcompliance/migrations/__init__.py +0 -0
- wbcompliance/models/__init__.py +20 -0
- wbcompliance/models/compliance_form.py +626 -0
- wbcompliance/models/compliance_task.py +800 -0
- wbcompliance/models/compliance_type.py +133 -0
- wbcompliance/models/enums.py +13 -0
- wbcompliance/models/risk_management/__init__.py +4 -0
- wbcompliance/models/risk_management/backend.py +139 -0
- wbcompliance/models/risk_management/checks.py +194 -0
- wbcompliance/models/risk_management/dispatch.py +41 -0
- wbcompliance/models/risk_management/incidents.py +619 -0
- wbcompliance/models/risk_management/mixins.py +115 -0
- wbcompliance/models/risk_management/rules.py +654 -0
- wbcompliance/permissions.py +32 -0
- wbcompliance/serializers/__init__.py +30 -0
- wbcompliance/serializers/compliance_form.py +320 -0
- wbcompliance/serializers/compliance_task.py +463 -0
- wbcompliance/serializers/compliance_type.py +26 -0
- wbcompliance/serializers/risk_management/__init__.py +19 -0
- wbcompliance/serializers/risk_management/checks.py +53 -0
- wbcompliance/serializers/risk_management/incidents.py +227 -0
- wbcompliance/serializers/risk_management/rules.py +158 -0
- wbcompliance/tasks.py +112 -0
- wbcompliance/tests/__init__.py +0 -0
- wbcompliance/tests/conftest.py +63 -0
- wbcompliance/tests/disable_signals.py +82 -0
- wbcompliance/tests/mixins.py +17 -0
- wbcompliance/tests/risk_management/__init__.py +0 -0
- wbcompliance/tests/risk_management/models/__init__.py +0 -0
- wbcompliance/tests/risk_management/models/test_backends.py +0 -0
- wbcompliance/tests/risk_management/models/test_checks.py +55 -0
- wbcompliance/tests/risk_management/models/test_incidents.py +327 -0
- wbcompliance/tests/risk_management/models/test_rules.py +255 -0
- wbcompliance/tests/signals.py +89 -0
- wbcompliance/tests/test_filters.py +23 -0
- wbcompliance/tests/test_models.py +57 -0
- wbcompliance/tests/test_serializers.py +48 -0
- wbcompliance/tests/test_views.py +377 -0
- wbcompliance/tests/tests.py +21 -0
- wbcompliance/urls.py +238 -0
- wbcompliance/viewsets/__init__.py +40 -0
- wbcompliance/viewsets/buttons/__init__.py +9 -0
- wbcompliance/viewsets/buttons/compliance_form.py +78 -0
- wbcompliance/viewsets/buttons/compliance_task.py +149 -0
- wbcompliance/viewsets/buttons/risk_managment/__init__.py +3 -0
- wbcompliance/viewsets/buttons/risk_managment/checks.py +11 -0
- wbcompliance/viewsets/buttons/risk_managment/incidents.py +51 -0
- wbcompliance/viewsets/buttons/risk_managment/rules.py +35 -0
- wbcompliance/viewsets/compliance_form.py +425 -0
- wbcompliance/viewsets/compliance_task.py +513 -0
- wbcompliance/viewsets/compliance_type.py +38 -0
- wbcompliance/viewsets/display/__init__.py +22 -0
- wbcompliance/viewsets/display/compliance_form.py +317 -0
- wbcompliance/viewsets/display/compliance_task.py +453 -0
- wbcompliance/viewsets/display/compliance_type.py +22 -0
- wbcompliance/viewsets/display/risk_managment/__init__.py +11 -0
- wbcompliance/viewsets/display/risk_managment/checks.py +46 -0
- wbcompliance/viewsets/display/risk_managment/incidents.py +155 -0
- wbcompliance/viewsets/display/risk_managment/rules.py +146 -0
- wbcompliance/viewsets/display/risk_managment/tables.py +51 -0
- wbcompliance/viewsets/endpoints/__init__.py +27 -0
- wbcompliance/viewsets/endpoints/compliance_form.py +207 -0
- wbcompliance/viewsets/endpoints/compliance_task.py +193 -0
- wbcompliance/viewsets/endpoints/compliance_type.py +9 -0
- wbcompliance/viewsets/endpoints/risk_managment/__init__.py +12 -0
- wbcompliance/viewsets/endpoints/risk_managment/checks.py +16 -0
- wbcompliance/viewsets/endpoints/risk_managment/incidents.py +36 -0
- wbcompliance/viewsets/endpoints/risk_managment/rules.py +32 -0
- wbcompliance/viewsets/endpoints/risk_managment/tables.py +14 -0
- wbcompliance/viewsets/menu/__init__.py +17 -0
- wbcompliance/viewsets/menu/compliance_form.py +49 -0
- wbcompliance/viewsets/menu/compliance_task.py +130 -0
- wbcompliance/viewsets/menu/compliance_type.py +17 -0
- wbcompliance/viewsets/menu/risk_management.py +56 -0
- wbcompliance/viewsets/risk_management/__init__.py +21 -0
- wbcompliance/viewsets/risk_management/checks.py +49 -0
- wbcompliance/viewsets/risk_management/incidents.py +204 -0
- wbcompliance/viewsets/risk_management/mixins.py +52 -0
- wbcompliance/viewsets/risk_management/rules.py +179 -0
- wbcompliance/viewsets/risk_management/tables.py +96 -0
- wbcompliance/viewsets/titles/__init__.py +17 -0
- wbcompliance/viewsets/titles/compliance_form.py +101 -0
- wbcompliance/viewsets/titles/compliance_task.py +60 -0
- wbcompliance/viewsets/titles/compliance_type.py +13 -0
- wbcompliance/viewsets/titles/risk_managment/__init__.py +1 -0
- wbcompliance/viewsets/titles/risk_managment/checks.py +0 -0
- wbcompliance/viewsets/titles/risk_managment/incidents.py +0 -0
- wbcompliance/viewsets/titles/risk_managment/rules.py +0 -0
- wbcompliance/viewsets/titles/risk_managment/tables.py +7 -0
- wbcompliance-2.2.1.dist-info/METADATA +7 -0
- wbcompliance-2.2.1.dist-info/RECORD +129 -0
- wbcompliance-2.2.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import pytest
|
|
5
|
+
from faker import Faker
|
|
6
|
+
from pandas.tseries.offsets import BDay
|
|
7
|
+
|
|
8
|
+
fake = Faker()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.django_db
|
|
12
|
+
class TestChecks:
|
|
13
|
+
def test_init(self, risk_check):
|
|
14
|
+
assert risk_check
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize("evaluation_date", [fake.date_object()])
|
|
17
|
+
def test_previous_check(self, risk_rule, risk_check_factory, rule_checked_object_relationship, evaluation_date):
|
|
18
|
+
check1 = risk_check_factory.create(
|
|
19
|
+
rule_checked_object_relationship=rule_checked_object_relationship, evaluation_date=evaluation_date
|
|
20
|
+
)
|
|
21
|
+
assert not check1.previous_check
|
|
22
|
+
check2 = risk_check_factory.create(
|
|
23
|
+
rule_checked_object_relationship=rule_checked_object_relationship, evaluation_date=evaluation_date
|
|
24
|
+
)
|
|
25
|
+
check3 = risk_check_factory.create(
|
|
26
|
+
rule_checked_object_relationship=rule_checked_object_relationship,
|
|
27
|
+
evaluation_date=evaluation_date + timedelta(days=1),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
assert check2.previous_check is None
|
|
31
|
+
assert check3.previous_check == check2
|
|
32
|
+
# Shouldn't include this check in the queryset
|
|
33
|
+
other_check = risk_check_factory.create(
|
|
34
|
+
rule_checked_object_relationship=rule_checked_object_relationship,
|
|
35
|
+
evaluation_date=evaluation_date + timedelta(days=1),
|
|
36
|
+
)
|
|
37
|
+
assert other_check.previous_check == check2
|
|
38
|
+
|
|
39
|
+
def test_get_unchecked_dates(
|
|
40
|
+
self, weekday, risk_rule, risk_check_factory, rule_checked_object_relationship_factory
|
|
41
|
+
):
|
|
42
|
+
rel = rule_checked_object_relationship_factory.create(rule=risk_rule)
|
|
43
|
+
maximum_day_interval = 10
|
|
44
|
+
assert list(rel.get_unchecked_dates(to_date=weekday, maximum_day_interval=maximum_day_interval)) == list(
|
|
45
|
+
map(lambda _d: _d.date(), pd.date_range(weekday - timedelta(days=maximum_day_interval), weekday, freq="B"))
|
|
46
|
+
)
|
|
47
|
+
risk_check = risk_check_factory.create(rule_checked_object_relationship=rel, evaluation_date=weekday)
|
|
48
|
+
|
|
49
|
+
assert list(rel.get_unchecked_dates()) == [(risk_check.evaluation_date + BDay(1)).date()]
|
|
50
|
+
assert list(rel.get_unchecked_dates(to_date=risk_check.evaluation_date)) == list()
|
|
51
|
+
|
|
52
|
+
risk_check2 = risk_check_factory.create(
|
|
53
|
+
rule_checked_object_relationship=rel, evaluation_date=risk_check.evaluation_date + BDay(1)
|
|
54
|
+
)
|
|
55
|
+
assert list(rel.get_unchecked_dates()) == [(risk_check2.evaluation_date + BDay(1)).date()]
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from django.contrib.auth import get_user_model
|
|
6
|
+
from django.contrib.auth.models import Permission
|
|
7
|
+
from django.contrib.contenttypes.models import ContentType
|
|
8
|
+
from django.core.exceptions import ValidationError
|
|
9
|
+
from faker import Faker
|
|
10
|
+
from guardian.shortcuts import assign_perm
|
|
11
|
+
from guardian.utils import get_anonymous_user
|
|
12
|
+
from pandas.tseries.offsets import BDay
|
|
13
|
+
from wbcompliance.models.risk_management.incidents import (
|
|
14
|
+
RiskIncident,
|
|
15
|
+
resolve_all_incidents_as_task,
|
|
16
|
+
)
|
|
17
|
+
from wbcompliance.models.risk_management.rules import RiskRule
|
|
18
|
+
|
|
19
|
+
User = get_user_model()
|
|
20
|
+
|
|
21
|
+
fake = Faker()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.django_db
|
|
25
|
+
class TestRiskIncidentFactory:
|
|
26
|
+
def test_init(self, risk_incident):
|
|
27
|
+
assert risk_incident.computed_str
|
|
28
|
+
|
|
29
|
+
def test_closing_incident(self, risk_incident, checked_object_incident_relationship_factory):
|
|
30
|
+
checked_object_incident_relationship_factory.create(incident=risk_incident)
|
|
31
|
+
assert risk_incident.checked_object_relationships.filter(status=RiskIncident.Status.OPEN).exists()
|
|
32
|
+
risk_incident.status = RiskIncident.Status.CLOSED
|
|
33
|
+
risk_incident.save()
|
|
34
|
+
assert not risk_incident.checked_object_relationships.filter(status=RiskIncident.Status.OPEN).exists()
|
|
35
|
+
|
|
36
|
+
def test_incident_date_range(
|
|
37
|
+
self,
|
|
38
|
+
risk_incident,
|
|
39
|
+
checked_object_incident_relationship_factory,
|
|
40
|
+
risk_check_factory,
|
|
41
|
+
rule_checked_object_relationship_factory,
|
|
42
|
+
):
|
|
43
|
+
rel1 = checked_object_incident_relationship_factory.create(incident=risk_incident)
|
|
44
|
+
checked_object_relationship = rule_checked_object_relationship_factory.create(rule=risk_incident.rule)
|
|
45
|
+
for i in range(7):
|
|
46
|
+
next_date = (rel1.incident_date + BDay(i)).date()
|
|
47
|
+
check = risk_check_factory.create(
|
|
48
|
+
evaluation_date=next_date, rule_checked_object_relationship=checked_object_relationship
|
|
49
|
+
)
|
|
50
|
+
checked_object_incident_relationship_factory.create(incident=risk_incident, rule_check=check)
|
|
51
|
+
risk_incident.refresh_from_db()
|
|
52
|
+
assert risk_incident.date_range.upper == next_date + timedelta(days=1)
|
|
53
|
+
assert RiskIncident.objects.count() == 1
|
|
54
|
+
|
|
55
|
+
def test_incident_always_max_severity(
|
|
56
|
+
self,
|
|
57
|
+
risk_incident_factory,
|
|
58
|
+
checked_object_incident_relationship_factory,
|
|
59
|
+
risk_incident_type_factory,
|
|
60
|
+
):
|
|
61
|
+
minor_severity = risk_incident_type_factory.create(severity_order=0)
|
|
62
|
+
major_severity = risk_incident_type_factory.create(severity_order=1)
|
|
63
|
+
critical_severity = risk_incident_type_factory.create(severity_order=2)
|
|
64
|
+
|
|
65
|
+
incident = risk_incident_factory.create(severity=minor_severity)
|
|
66
|
+
assert incident.severity == minor_severity
|
|
67
|
+
|
|
68
|
+
checked_object_incident_relationship_factory.create(incident=incident, severity=minor_severity)
|
|
69
|
+
incident.save()
|
|
70
|
+
assert incident.severity == minor_severity
|
|
71
|
+
|
|
72
|
+
checked_object_incident_relationship_factory.create(incident=incident, severity=major_severity)
|
|
73
|
+
incident.save()
|
|
74
|
+
assert incident.severity == major_severity
|
|
75
|
+
|
|
76
|
+
incident.severity = critical_severity
|
|
77
|
+
incident.save()
|
|
78
|
+
assert incident.severity == critical_severity
|
|
79
|
+
|
|
80
|
+
@pytest.mark.parametrize(
|
|
81
|
+
"evaluation_date, is_breached_object",
|
|
82
|
+
[
|
|
83
|
+
(fake.date_object(), True),
|
|
84
|
+
(fake.date_object(), False),
|
|
85
|
+
],
|
|
86
|
+
)
|
|
87
|
+
def test_get_or_create_incident(self, risk_rule, risk_incident_type, evaluation_date, is_breached_object):
|
|
88
|
+
if is_breached_object:
|
|
89
|
+
breached_object = get_anonymous_user()
|
|
90
|
+
breached_object_repr = str(get_anonymous_user())
|
|
91
|
+
else:
|
|
92
|
+
breached_object = None
|
|
93
|
+
breached_object_repr = fake.text(max_nb_chars=125)
|
|
94
|
+
|
|
95
|
+
assert not RiskIncident.objects.exists()
|
|
96
|
+
risk_rule.get_or_create_incident(evaluation_date, risk_incident_type, breached_object, breached_object_repr)
|
|
97
|
+
if breached_object:
|
|
98
|
+
assert (
|
|
99
|
+
RiskIncident.objects.filter(
|
|
100
|
+
rule=risk_rule,
|
|
101
|
+
date_range__contains=evaluation_date,
|
|
102
|
+
breached_content_type=ContentType.objects.get_for_model(breached_object),
|
|
103
|
+
breached_object_id=breached_object.id,
|
|
104
|
+
).count()
|
|
105
|
+
== 1
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
assert (
|
|
109
|
+
RiskIncident.objects.filter(
|
|
110
|
+
rule=risk_rule, date_range__contains=evaluation_date, breached_object_repr=breached_object_repr
|
|
111
|
+
).count()
|
|
112
|
+
== 1
|
|
113
|
+
)
|
|
114
|
+
assert RiskIncident.objects.count() == 1
|
|
115
|
+
# Check if we reevaluate this incident one business day later, the incident is consider the same
|
|
116
|
+
risk_rule.get_or_create_incident(
|
|
117
|
+
(evaluation_date + BDay(1)).date(), risk_incident_type, breached_object, breached_object_repr
|
|
118
|
+
)
|
|
119
|
+
assert RiskIncident.objects.count() == 1
|
|
120
|
+
|
|
121
|
+
# Check if we reevaluate this incident three business day later, the incident is consider the discontinue, and then a new incident is created
|
|
122
|
+
risk_rule.get_or_create_incident(
|
|
123
|
+
(evaluation_date + BDay(3)).date(), risk_incident_type, breached_object, breached_object_repr
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
assert RiskIncident.objects.count() == 2
|
|
127
|
+
|
|
128
|
+
def test_get_or_create_incident_with_resolve_incident(
|
|
129
|
+
self, risk_incident, checked_object_incident_relationship_factory
|
|
130
|
+
):
|
|
131
|
+
subincident = checked_object_incident_relationship_factory.create(incident=risk_incident)
|
|
132
|
+
assert risk_incident.status == RiskIncident.Status.OPEN
|
|
133
|
+
risk_incident.resolve()
|
|
134
|
+
risk_incident.save()
|
|
135
|
+
assert risk_incident.status == RiskIncident.Status.RESOLVED
|
|
136
|
+
new_evaluation_date = subincident.rule_check.evaluation_date + timedelta(days=1)
|
|
137
|
+
incident, created = risk_incident.rule.get_or_create_incident(
|
|
138
|
+
new_evaluation_date,
|
|
139
|
+
subincident.severity,
|
|
140
|
+
risk_incident.breached_content_object,
|
|
141
|
+
risk_incident.breached_object_repr,
|
|
142
|
+
)
|
|
143
|
+
assert incident == risk_incident
|
|
144
|
+
assert incident.date_range.upper == new_evaluation_date
|
|
145
|
+
|
|
146
|
+
@pytest.mark.parametrize("incident_report", [fake.paragraph()])
|
|
147
|
+
def test_update_or_create_relationship(
|
|
148
|
+
self, risk_incident_factory, risk_check, incident_report, risk_incident_type
|
|
149
|
+
):
|
|
150
|
+
risk_incident = risk_incident_factory.create(rule=risk_check.rule_checked_object_relationship.rule)
|
|
151
|
+
assert not risk_incident.checked_object_relationships.exists()
|
|
152
|
+
risk_incident.update_or_create_relationship(
|
|
153
|
+
risk_check, incident_report, dict(), fake.pyfloat(), risk_incident_type
|
|
154
|
+
)
|
|
155
|
+
assert risk_incident.checked_object_relationships.filter(
|
|
156
|
+
rule_check=risk_check, severity=risk_incident_type
|
|
157
|
+
).exists()
|
|
158
|
+
|
|
159
|
+
@pytest.mark.parametrize(
|
|
160
|
+
"incident_report, override_incident",
|
|
161
|
+
[
|
|
162
|
+
(fake.paragraph(), True),
|
|
163
|
+
(fake.paragraph(), False),
|
|
164
|
+
],
|
|
165
|
+
)
|
|
166
|
+
def test_update_or_create_relationship_already_existing(
|
|
167
|
+
self, risk_incident_factory, risk_check_factory, incident_report, risk_incident_type, override_incident
|
|
168
|
+
):
|
|
169
|
+
check1 = risk_check_factory.create()
|
|
170
|
+
check2 = risk_check_factory.create(
|
|
171
|
+
rule_checked_object_relationship=check1.rule_checked_object_relationship,
|
|
172
|
+
evaluation_date=check1.evaluation_date,
|
|
173
|
+
)
|
|
174
|
+
risk_incident = risk_incident_factory.create(rule=check1.rule_checked_object_relationship.rule)
|
|
175
|
+
|
|
176
|
+
# We check the first time
|
|
177
|
+
initial_breached_value = str(fake.pyfloat())
|
|
178
|
+
risk_incident.update_or_create_relationship(
|
|
179
|
+
check1, incident_report, dict(), initial_breached_value, risk_incident_type
|
|
180
|
+
)
|
|
181
|
+
# verify that the relationship was created
|
|
182
|
+
assert risk_incident.checked_object_relationships.count() == 1
|
|
183
|
+
assert (
|
|
184
|
+
risk_incident.checked_object_relationships.filter(rule_check=check1, severity=risk_incident_type).count()
|
|
185
|
+
== 1
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Closing incident because open incident are override by default
|
|
189
|
+
risk_incident.status = RiskIncident.Status.CLOSED
|
|
190
|
+
risk_incident.save()
|
|
191
|
+
breached_value = str(fake.pyfloat())
|
|
192
|
+
risk_incident.update_or_create_relationship(
|
|
193
|
+
check2, incident_report, dict(), breached_value, risk_incident_type, override_incident
|
|
194
|
+
)
|
|
195
|
+
subincidents = risk_incident.checked_object_relationships.all()
|
|
196
|
+
if not override_incident:
|
|
197
|
+
assert subincidents.get(rule_check=check1).breached_value == initial_breached_value
|
|
198
|
+
assert subincidents.get(rule_check=check2).breached_value == breached_value
|
|
199
|
+
else:
|
|
200
|
+
assert subincidents.get(rule_check=check1).breached_value == breached_value
|
|
201
|
+
assert not subincidents.filter(rule_check=check2).exists()
|
|
202
|
+
# asser the parent incident were reopened
|
|
203
|
+
risk_incident.refresh_from_db()
|
|
204
|
+
if not override_incident:
|
|
205
|
+
assert risk_incident.status == RiskIncident.Status.OPEN
|
|
206
|
+
else:
|
|
207
|
+
assert risk_incident.status == RiskIncident.Status.CLOSED
|
|
208
|
+
|
|
209
|
+
if not override_incident:
|
|
210
|
+
risk_incident.status = RiskIncident.Status.CLOSED
|
|
211
|
+
risk_incident.save()
|
|
212
|
+
# we submit a new check the next day with the same breached value. We test if the parent incident remains close because of it
|
|
213
|
+
check3 = risk_check_factory.create(
|
|
214
|
+
rule_checked_object_relationship=check1.rule_checked_object_relationship,
|
|
215
|
+
evaluation_date=check1.evaluation_date + timedelta(days=1),
|
|
216
|
+
)
|
|
217
|
+
risk_incident.update_or_create_relationship(
|
|
218
|
+
check3, incident_report, dict(), breached_value, risk_incident_type
|
|
219
|
+
)
|
|
220
|
+
risk_incident.refresh_from_db()
|
|
221
|
+
assert risk_incident.status == RiskIncident.Status.CLOSED
|
|
222
|
+
assert (
|
|
223
|
+
subincidents.get(rule_check=check3, breached_value=breached_value).status == RiskIncident.Status.CLOSED
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
@patch.object(RiskIncident, "resolve_all_incidents")
|
|
227
|
+
@pytest.mark.parametrize("reviewer_comment", [(fake.paragraph())])
|
|
228
|
+
def test_resolve_all_incidents_as_task(self, mock_function, risk_incident, user, reviewer_comment):
|
|
229
|
+
resolve_all_incidents_as_task(user.id, reviewer_comment, risk_incident.id)
|
|
230
|
+
assert mock_function.call_count == 1
|
|
231
|
+
|
|
232
|
+
@pytest.mark.parametrize(
|
|
233
|
+
"is_superuser, is_admin",
|
|
234
|
+
[
|
|
235
|
+
(True, True),
|
|
236
|
+
(False, True),
|
|
237
|
+
(False, False),
|
|
238
|
+
(True, False),
|
|
239
|
+
(False, False),
|
|
240
|
+
],
|
|
241
|
+
)
|
|
242
|
+
def test_can_manage_without_incident(self, user, is_superuser, is_admin):
|
|
243
|
+
if is_superuser:
|
|
244
|
+
user.is_superuser = True
|
|
245
|
+
user.save()
|
|
246
|
+
if is_admin:
|
|
247
|
+
admin_perm = Permission.objects.get(codename="administrate_riskrule")
|
|
248
|
+
user.user_permissions.add(admin_perm)
|
|
249
|
+
res = RiskIncident.can_manage(user)
|
|
250
|
+
assert res == (is_admin or is_superuser)
|
|
251
|
+
|
|
252
|
+
def test_can_manage_with_incident(self, user, risk_incident):
|
|
253
|
+
assert not RiskIncident.can_manage(user, risk_incident.rule)
|
|
254
|
+
assign_perm(RiskRule.change_perm_str, user, risk_incident.rule)
|
|
255
|
+
assert RiskIncident.can_manage(user, risk_incident.rule)
|
|
256
|
+
|
|
257
|
+
@pytest.mark.parametrize("risk_rule__automatically_close_incident", [True, False])
|
|
258
|
+
def test_post_workflow_change_status(self, risk_incident_factory, risk_rule):
|
|
259
|
+
risk_incident = risk_incident_factory.create(rule=risk_rule)
|
|
260
|
+
assert not risk_incident.status == RiskIncident.Status.CLOSED
|
|
261
|
+
risk_incident.post_workflow()
|
|
262
|
+
assert (
|
|
263
|
+
risk_incident.status == RiskIncident.Status.CLOSED
|
|
264
|
+
if risk_incident.rule.automatically_close_incident
|
|
265
|
+
else RiskIncident.Status.OPEN
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def test_post_workflow_elevate_incident(self, risk_incident, rule_threshold_factory):
|
|
269
|
+
current_threshold = risk_incident.threshold
|
|
270
|
+
current_threshold.upgradable_after_days = risk_incident.business_days - 1
|
|
271
|
+
current_threshold.save()
|
|
272
|
+
next_threshold_1 = rule_threshold_factory.create(
|
|
273
|
+
rule=risk_incident.rule, upgradable_after_days=risk_incident.business_days - 1
|
|
274
|
+
)
|
|
275
|
+
assert next_threshold_1.severity.severity_order == 1
|
|
276
|
+
next_threshold_2 = rule_threshold_factory.create(rule=risk_incident.rule)
|
|
277
|
+
assert next_threshold_2.severity.severity_order == 2
|
|
278
|
+
assert risk_incident.threshold == current_threshold
|
|
279
|
+
risk_incident.post_workflow()
|
|
280
|
+
assert risk_incident.threshold == next_threshold_1
|
|
281
|
+
risk_incident.post_workflow()
|
|
282
|
+
assert risk_incident.threshold == next_threshold_2
|
|
283
|
+
risk_incident.post_workflow()
|
|
284
|
+
assert risk_incident.threshold == next_threshold_2
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@pytest.mark.django_db
|
|
288
|
+
class TestCheckedObjectIncidentRelationshipFactory:
|
|
289
|
+
def test_init(self, rule_checked_object_relationship):
|
|
290
|
+
assert rule_checked_object_relationship
|
|
291
|
+
|
|
292
|
+
def test_init_checked_object_not_valid(
|
|
293
|
+
self,
|
|
294
|
+
checked_object_incident_relationship_factory,
|
|
295
|
+
rule_checked_object_relationship_factory,
|
|
296
|
+
risk_incident_factory,
|
|
297
|
+
risk_check_factory,
|
|
298
|
+
):
|
|
299
|
+
rel_user = rule_checked_object_relationship_factory.create()
|
|
300
|
+
rel_other = rule_checked_object_relationship_factory.create(
|
|
301
|
+
checked_object_content_type=ContentType.objects.get_for_model(rel_user)
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
incident = risk_incident_factory.create(rule=rel_user.rule)
|
|
305
|
+
check = risk_check_factory.create(rule_checked_object_relationship=rel_other)
|
|
306
|
+
with pytest.raises(ValidationError):
|
|
307
|
+
checked_object_incident_relationship_factory.create(incident=incident, rule_check=check)
|
|
308
|
+
|
|
309
|
+
def test_init_backend_allowed_content_type_not_valid(
|
|
310
|
+
self,
|
|
311
|
+
checked_object_incident_relationship_factory,
|
|
312
|
+
rule_checked_object_relationship_factory,
|
|
313
|
+
risk_incident_factory,
|
|
314
|
+
):
|
|
315
|
+
rel_user = rule_checked_object_relationship_factory.create()
|
|
316
|
+
rel_user.rule.rule_backend.allowed_checked_object_content_type = ContentType.objects.get_for_model(rel_user)
|
|
317
|
+
rel_user.rule.rule_backend.save()
|
|
318
|
+
incident = risk_incident_factory.create(rule=rel_user.rule)
|
|
319
|
+
with pytest.raises(ValidationError):
|
|
320
|
+
checked_object_incident_relationship_factory.create(incident=incident)
|
|
321
|
+
|
|
322
|
+
def test_checked_object(self, checked_object_incident_relationship):
|
|
323
|
+
assert checked_object_incident_relationship.checked_object
|
|
324
|
+
assert (
|
|
325
|
+
checked_object_incident_relationship.checked_object
|
|
326
|
+
== checked_object_incident_relationship.rule_check.rule_checked_object_relationship.checked_object
|
|
327
|
+
)
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from datetime import date, timedelta
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from django.contrib.auth.models import Group
|
|
7
|
+
from django.contrib.contenttypes.models import ContentType
|
|
8
|
+
from django.core.exceptions import ValidationError
|
|
9
|
+
from faker import Faker
|
|
10
|
+
from guardian.utils import get_anonymous_user
|
|
11
|
+
from psycopg.types.range import NumericRange
|
|
12
|
+
from wbcompliance.models import (
|
|
13
|
+
RiskIncident,
|
|
14
|
+
RiskRule,
|
|
15
|
+
RuleCheckedObjectRelationship,
|
|
16
|
+
RuleThreshold,
|
|
17
|
+
)
|
|
18
|
+
from wbcompliance.models.risk_management.rules import process_rule_as_task
|
|
19
|
+
from wbcore.permissions.utils import perm_to_permission
|
|
20
|
+
|
|
21
|
+
fake = Faker()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.django_db
|
|
25
|
+
class TestRuleCheckedObjectRelationship:
|
|
26
|
+
def test_init(self, rule_checked_object_relationship):
|
|
27
|
+
assert rule_checked_object_relationship
|
|
28
|
+
|
|
29
|
+
def test_init_wrong_content_type(self, risk_rule, rule_checked_object_relationship_factory):
|
|
30
|
+
risk_rule.rule_backend.allowed_checked_object_content_type = ContentType.objects.get_for_model(RuleThreshold)
|
|
31
|
+
risk_rule.rule_backend.save()
|
|
32
|
+
with pytest.raises(ValidationError):
|
|
33
|
+
rule_checked_object_relationship_factory.create(
|
|
34
|
+
rule=risk_rule,
|
|
35
|
+
checked_object_content_type=ContentType.objects.get_for_model(RuleCheckedObjectRelationship),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.django_db
|
|
40
|
+
class TestRuleBackend:
|
|
41
|
+
def test_init(self, rule_backend):
|
|
42
|
+
assert rule_backend
|
|
43
|
+
|
|
44
|
+
def test_backend_class(self, rule_backend):
|
|
45
|
+
assert rule_backend.backend_class.__module__
|
|
46
|
+
assert rule_backend.backend_class.__class__
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize("evaluation_date", [fake.date_object()])
|
|
49
|
+
def test_backend(self, rule_backend, risk_rule, evaluation_date):
|
|
50
|
+
assert rule_backend.backend(evaluation_date, risk_rule, risk_rule.parameters, RuleThreshold.objects.none())
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.django_db
|
|
54
|
+
class TestRuleThreshold:
|
|
55
|
+
def test_init(self, rule_threshold):
|
|
56
|
+
assert rule_threshold
|
|
57
|
+
|
|
58
|
+
@pytest.mark.parametrize(
|
|
59
|
+
"lower,upper,value,result",
|
|
60
|
+
[
|
|
61
|
+
(0, 1, random.random(), True),
|
|
62
|
+
(1, 2, random.random(), False),
|
|
63
|
+
(-1, 0, random.random(), False),
|
|
64
|
+
(None, 1, random.random(), True),
|
|
65
|
+
(0, None, random.random(), True),
|
|
66
|
+
],
|
|
67
|
+
)
|
|
68
|
+
def test_is_inrange(self, rule_threshold_factory, lower, upper, value, result):
|
|
69
|
+
threshold = rule_threshold_factory.create(range=NumericRange(lower=lower, upper=upper))
|
|
70
|
+
assert threshold.is_inrange(value) == result
|
|
71
|
+
|
|
72
|
+
def test_get_notifiable_users(self, rule_threshold, user_factory):
|
|
73
|
+
assert not rule_threshold.get_notifiable_users().exists()
|
|
74
|
+
user = user_factory.create()
|
|
75
|
+
user_group = user_factory.create()
|
|
76
|
+
group_test = Group.objects.create(name="test")
|
|
77
|
+
user_group.groups.add(group_test)
|
|
78
|
+
rule_threshold.notifiable_users.add(user.profile) # Add user to threshold person group
|
|
79
|
+
rule_threshold.notifiable_groups.add(group_test) # Add user to threshold group
|
|
80
|
+
users = rule_threshold.get_notifiable_users()
|
|
81
|
+
assert set(users.values_list("id", flat=True)) == {user.id, user_group.id}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.mark.django_db
|
|
85
|
+
class TestRiskRule:
|
|
86
|
+
def test_init(self, risk_rule):
|
|
87
|
+
assert risk_rule
|
|
88
|
+
|
|
89
|
+
def test_checked_objects(self, risk_rule_factory, rule_threshold_factory):
|
|
90
|
+
rule = risk_rule_factory.create()
|
|
91
|
+
other_rule = risk_rule_factory.create()
|
|
92
|
+
threshold1 = rule_threshold_factory.create()
|
|
93
|
+
threshold2 = rule_threshold_factory.create()
|
|
94
|
+
RuleCheckedObjectRelationship.objects.create(checked_object=threshold1, rule=rule)
|
|
95
|
+
RuleCheckedObjectRelationship.objects.create(checked_object=get_anonymous_user(), rule=rule)
|
|
96
|
+
RuleCheckedObjectRelationship.objects.create(checked_object=threshold2, rule=other_rule)
|
|
97
|
+
assert set(rule.checked_objects) == {threshold1, get_anonymous_user()}
|
|
98
|
+
|
|
99
|
+
def test_checks(self, risk_rule_factory, rule_checked_object_relationship_factory, risk_check_factory):
|
|
100
|
+
rule = risk_rule_factory.create()
|
|
101
|
+
other_rule = risk_rule_factory.create()
|
|
102
|
+
rel1 = rule_checked_object_relationship_factory.create(rule=rule)
|
|
103
|
+
rel2 = rule_checked_object_relationship_factory.create(rule=rule)
|
|
104
|
+
other_rel = rule_checked_object_relationship_factory.create(rule=other_rule)
|
|
105
|
+
check1 = risk_check_factory.create(rule_checked_object_relationship=rel1)
|
|
106
|
+
check2 = risk_check_factory.create(rule_checked_object_relationship=rel2)
|
|
107
|
+
risk_check_factory.create(rule_checked_object_relationship=other_rel)
|
|
108
|
+
assert set(rule.checks.values_list("id", flat=True)) == {check1.id, check2.id}
|
|
109
|
+
|
|
110
|
+
@pytest.mark.parametrize(
|
|
111
|
+
"risk_rule__automatically_close_incident, evaluation_date, expected_incident_status",
|
|
112
|
+
[
|
|
113
|
+
(True, fake.date_object(), RiskIncident.Status.CLOSED),
|
|
114
|
+
(False, fake.date_object(), RiskIncident.Status.OPEN),
|
|
115
|
+
],
|
|
116
|
+
)
|
|
117
|
+
def test_process_rule(
|
|
118
|
+
self,
|
|
119
|
+
risk_rule,
|
|
120
|
+
rule_checked_object_relationship_factory,
|
|
121
|
+
rule_threshold_factory,
|
|
122
|
+
evaluation_date,
|
|
123
|
+
expected_incident_status,
|
|
124
|
+
):
|
|
125
|
+
rule_threshold_factory.create(rule=risk_rule)
|
|
126
|
+
rule_checked_object_relationship_factory.create(rule=risk_rule)
|
|
127
|
+
|
|
128
|
+
risk_rule.process_rule(evaluation_date)
|
|
129
|
+
incident = RiskIncident.objects.get(rule=risk_rule, status=expected_incident_status)
|
|
130
|
+
assert incident.checked_object_relationships.count() == 1
|
|
131
|
+
|
|
132
|
+
def test_get_permissions_for_user(self, risk_rule, user_factory):
|
|
133
|
+
user_with_permission = user_factory.create()
|
|
134
|
+
user_without_permission = user_factory.create()
|
|
135
|
+
user_with_permission.user_permissions.add(perm_to_permission(RiskRule.view_perm_str))
|
|
136
|
+
assert risk_rule.get_permissions_for_user(user_with_permission)[RiskRule.view_perm_str] is False
|
|
137
|
+
assert not risk_rule.get_permissions_for_user(user_without_permission)
|
|
138
|
+
|
|
139
|
+
def test_get_rules_for_object(self, risk_rule_factory, rule_checked_object_relationship_factory):
|
|
140
|
+
rule1 = risk_rule_factory.create()
|
|
141
|
+
rule2 = risk_rule_factory.create()
|
|
142
|
+
|
|
143
|
+
rule_checked_object_relationship_factory.create(rule=rule1)
|
|
144
|
+
rule_checked_object_relationship_factory.create(rule=rule2)
|
|
145
|
+
|
|
146
|
+
res = RiskRule.get_rules_for_object(get_anonymous_user())
|
|
147
|
+
assert res.exists()
|
|
148
|
+
assert set(res.values_list("id", flat=True)) == {rule1.id, rule2.id}
|
|
149
|
+
|
|
150
|
+
@patch.object(RiskRule, "process_rule")
|
|
151
|
+
@pytest.mark.parametrize(
|
|
152
|
+
"evaluation_date, override_incident",
|
|
153
|
+
[(fake.date_object(), fake.pybool())],
|
|
154
|
+
)
|
|
155
|
+
def test_process_rule_as_task(self, mock_function, risk_rule, evaluation_date, override_incident):
|
|
156
|
+
process_rule_as_task(risk_rule.id, evaluation_date, override_incident)
|
|
157
|
+
assert mock_function.call_count == 1
|
|
158
|
+
|
|
159
|
+
@patch("wbcompliance.models.risk_management.rules.send_notification")
|
|
160
|
+
def test_do_not_notify_no_open_incidents(
|
|
161
|
+
self,
|
|
162
|
+
mock_fct,
|
|
163
|
+
risk_incident_factory,
|
|
164
|
+
rule_checked_object_relationship,
|
|
165
|
+
risk_check_factory,
|
|
166
|
+
checked_object_incident_relationship_factory,
|
|
167
|
+
):
|
|
168
|
+
risk_rule = rule_checked_object_relationship.rule
|
|
169
|
+
risk_incident = risk_incident_factory.create(rule=risk_rule)
|
|
170
|
+
sub_incident = checked_object_incident_relationship_factory.create(incident=risk_incident)
|
|
171
|
+
|
|
172
|
+
incident_date = sub_incident.incident_date
|
|
173
|
+
risk_rule.notify(incident_date)
|
|
174
|
+
assert mock_fct.call_count == 1
|
|
175
|
+
|
|
176
|
+
risk_incident.refresh_from_db()
|
|
177
|
+
assert risk_incident.is_notified is True
|
|
178
|
+
|
|
179
|
+
# renotifying this incident still triggers the method because the incident (even marked as notified) is still open
|
|
180
|
+
risk_rule.notify(incident_date)
|
|
181
|
+
assert mock_fct.call_count == 2
|
|
182
|
+
|
|
183
|
+
risk_incident.ignore()
|
|
184
|
+
risk_incident.save()
|
|
185
|
+
risk_rule.notify(incident_date)
|
|
186
|
+
assert mock_fct.call_count == 2
|
|
187
|
+
|
|
188
|
+
tomorrow = incident_date + timedelta(days=1)
|
|
189
|
+
tomorrow_risk_check = risk_check_factory.create(
|
|
190
|
+
rule_checked_object_relationship=rule_checked_object_relationship, evaluation_date=tomorrow
|
|
191
|
+
)
|
|
192
|
+
tomorrow_sub_incident = checked_object_incident_relationship_factory.create( # noqa
|
|
193
|
+
rule_check=tomorrow_risk_check, incident=risk_incident
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
risk_incident.refresh_from_db()
|
|
197
|
+
assert risk_incident.status == RiskIncident.Status.OPEN
|
|
198
|
+
# the incident was already notified and is closed, so another subincident will mark the incident as open and retrigger notification
|
|
199
|
+
risk_rule.notify(tomorrow)
|
|
200
|
+
assert mock_fct.call_count == 3
|
|
201
|
+
|
|
202
|
+
after_tomorrow = tomorrow + timedelta(days=1)
|
|
203
|
+
risk_incident.ignore()
|
|
204
|
+
risk_incident.last_ignored_date = tomorrow
|
|
205
|
+
risk_incident.ignore_duration = timedelta(days=1)
|
|
206
|
+
risk_incident.save()
|
|
207
|
+
|
|
208
|
+
# this shoudn't reopen the incident
|
|
209
|
+
after_tomorrow_risk_check = risk_check_factory.create(
|
|
210
|
+
rule_checked_object_relationship=rule_checked_object_relationship, evaluation_date=after_tomorrow
|
|
211
|
+
)
|
|
212
|
+
after_tomorrow_sub_incident = checked_object_incident_relationship_factory.create( # noqa
|
|
213
|
+
rule_check=after_tomorrow_risk_check, incident=risk_incident
|
|
214
|
+
)
|
|
215
|
+
risk_incident.refresh_from_db()
|
|
216
|
+
assert risk_incident.status == RiskIncident.Status.IGNORED
|
|
217
|
+
# then notify shouldn't happen
|
|
218
|
+
risk_rule.notify(after_tomorrow)
|
|
219
|
+
assert mock_fct.call_count == 3
|
|
220
|
+
|
|
221
|
+
def test_is_evaluation_date_valid(self, risk_rule):
|
|
222
|
+
risk_rule.activation_date = None
|
|
223
|
+
risk_rule.save()
|
|
224
|
+
assert risk_rule.is_evaluation_date_valid(fake.date_object()) is False
|
|
225
|
+
|
|
226
|
+
activation_date = fake.date_object()
|
|
227
|
+
risk_rule.activation_date = activation_date
|
|
228
|
+
risk_rule.save()
|
|
229
|
+
assert (
|
|
230
|
+
risk_rule.is_evaluation_date_valid(
|
|
231
|
+
fake.date_between(activation_date, activation_date + timedelta(days=365))
|
|
232
|
+
)
|
|
233
|
+
is True
|
|
234
|
+
)
|
|
235
|
+
assert (
|
|
236
|
+
risk_rule.is_evaluation_date_valid(
|
|
237
|
+
fake.date_between(activation_date - timedelta(days=365), activation_date - timedelta(days=1))
|
|
238
|
+
)
|
|
239
|
+
is False
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# enable a rule to be checked every week on monday (i.e. every monday)
|
|
243
|
+
activation_date = date(2024, 11, 25) # Monday
|
|
244
|
+
risk_rule.activation_date = activation_date
|
|
245
|
+
risk_rule.frequency = "RRULE:FREQ=WEEKLY"
|
|
246
|
+
risk_rule.save()
|
|
247
|
+
|
|
248
|
+
assert risk_rule.is_evaluation_date_valid(date(2024, 11, 25)) is True
|
|
249
|
+
assert risk_rule.is_evaluation_date_valid(date(2024, 11, 26)) is False
|
|
250
|
+
assert risk_rule.is_evaluation_date_valid(date(2024, 11, 27)) is False
|
|
251
|
+
assert risk_rule.is_evaluation_date_valid(date(2024, 11, 28)) is False
|
|
252
|
+
assert risk_rule.is_evaluation_date_valid(date(2024, 11, 29)) is False
|
|
253
|
+
assert risk_rule.is_evaluation_date_valid(date(2024, 11, 30)) is False
|
|
254
|
+
assert risk_rule.is_evaluation_date_valid(date(2024, 12, 1)) is False
|
|
255
|
+
assert risk_rule.is_evaluation_date_valid(date(2024, 12, 2)) is True
|