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.
Files changed (129) hide show
  1. wbcompliance/__init__.py +1 -0
  2. wbcompliance/admin/__init__.py +16 -0
  3. wbcompliance/admin/compliance_form.py +56 -0
  4. wbcompliance/admin/compliance_task.py +135 -0
  5. wbcompliance/admin/compliance_type.py +8 -0
  6. wbcompliance/admin/risk_management/__init__.py +3 -0
  7. wbcompliance/admin/risk_management/checks.py +7 -0
  8. wbcompliance/admin/risk_management/incidents.py +50 -0
  9. wbcompliance/admin/risk_management/rules.py +63 -0
  10. wbcompliance/admin/utils.py +46 -0
  11. wbcompliance/apps.py +14 -0
  12. wbcompliance/factories/__init__.py +21 -0
  13. wbcompliance/factories/compliance.py +246 -0
  14. wbcompliance/factories/risk_management/__init__.py +12 -0
  15. wbcompliance/factories/risk_management/backends.py +42 -0
  16. wbcompliance/factories/risk_management/checks.py +12 -0
  17. wbcompliance/factories/risk_management/incidents.py +84 -0
  18. wbcompliance/factories/risk_management/rules.py +100 -0
  19. wbcompliance/filters/__init__.py +2 -0
  20. wbcompliance/filters/compliances.py +189 -0
  21. wbcompliance/filters/risk_management/__init__.py +3 -0
  22. wbcompliance/filters/risk_management/checks.py +22 -0
  23. wbcompliance/filters/risk_management/incidents.py +113 -0
  24. wbcompliance/filters/risk_management/rules.py +110 -0
  25. wbcompliance/filters/risk_management/tables.py +112 -0
  26. wbcompliance/filters/risk_management/utils.py +3 -0
  27. wbcompliance/management/__init__.py +10 -0
  28. wbcompliance/migrations/0001_initial_squashed_squashed_0010_alter_checkedobjectincidentrelationship_resolved_by_and_more.py +1744 -0
  29. wbcompliance/migrations/0011_alter_riskrule_parameters.py +21 -0
  30. wbcompliance/migrations/0012_alter_compliancetype_options.py +20 -0
  31. wbcompliance/migrations/0013_alter_riskrule_unique_together.py +16 -0
  32. wbcompliance/migrations/0014_alter_reviewcompliancetask_year.py +27 -0
  33. wbcompliance/migrations/0015_auto_20240103_0957.py +43 -0
  34. wbcompliance/migrations/0016_checkedobjectincidentrelationship_report_details_and_more.py +37 -0
  35. wbcompliance/migrations/0017_alter_rulebackend_incident_report_template.py +20 -0
  36. wbcompliance/migrations/0018_alter_rulecheckedobjectrelationship_unique_together.py +39 -0
  37. wbcompliance/migrations/0019_rulegroup_riskrule_activation_date_and_more.py +60 -0
  38. wbcompliance/migrations/__init__.py +0 -0
  39. wbcompliance/models/__init__.py +20 -0
  40. wbcompliance/models/compliance_form.py +626 -0
  41. wbcompliance/models/compliance_task.py +800 -0
  42. wbcompliance/models/compliance_type.py +133 -0
  43. wbcompliance/models/enums.py +13 -0
  44. wbcompliance/models/risk_management/__init__.py +4 -0
  45. wbcompliance/models/risk_management/backend.py +139 -0
  46. wbcompliance/models/risk_management/checks.py +194 -0
  47. wbcompliance/models/risk_management/dispatch.py +41 -0
  48. wbcompliance/models/risk_management/incidents.py +619 -0
  49. wbcompliance/models/risk_management/mixins.py +115 -0
  50. wbcompliance/models/risk_management/rules.py +654 -0
  51. wbcompliance/permissions.py +32 -0
  52. wbcompliance/serializers/__init__.py +30 -0
  53. wbcompliance/serializers/compliance_form.py +320 -0
  54. wbcompliance/serializers/compliance_task.py +463 -0
  55. wbcompliance/serializers/compliance_type.py +26 -0
  56. wbcompliance/serializers/risk_management/__init__.py +19 -0
  57. wbcompliance/serializers/risk_management/checks.py +53 -0
  58. wbcompliance/serializers/risk_management/incidents.py +227 -0
  59. wbcompliance/serializers/risk_management/rules.py +158 -0
  60. wbcompliance/tasks.py +112 -0
  61. wbcompliance/tests/__init__.py +0 -0
  62. wbcompliance/tests/conftest.py +63 -0
  63. wbcompliance/tests/disable_signals.py +82 -0
  64. wbcompliance/tests/mixins.py +17 -0
  65. wbcompliance/tests/risk_management/__init__.py +0 -0
  66. wbcompliance/tests/risk_management/models/__init__.py +0 -0
  67. wbcompliance/tests/risk_management/models/test_backends.py +0 -0
  68. wbcompliance/tests/risk_management/models/test_checks.py +55 -0
  69. wbcompliance/tests/risk_management/models/test_incidents.py +327 -0
  70. wbcompliance/tests/risk_management/models/test_rules.py +255 -0
  71. wbcompliance/tests/signals.py +89 -0
  72. wbcompliance/tests/test_filters.py +23 -0
  73. wbcompliance/tests/test_models.py +57 -0
  74. wbcompliance/tests/test_serializers.py +48 -0
  75. wbcompliance/tests/test_views.py +377 -0
  76. wbcompliance/tests/tests.py +21 -0
  77. wbcompliance/urls.py +238 -0
  78. wbcompliance/viewsets/__init__.py +40 -0
  79. wbcompliance/viewsets/buttons/__init__.py +9 -0
  80. wbcompliance/viewsets/buttons/compliance_form.py +78 -0
  81. wbcompliance/viewsets/buttons/compliance_task.py +149 -0
  82. wbcompliance/viewsets/buttons/risk_managment/__init__.py +3 -0
  83. wbcompliance/viewsets/buttons/risk_managment/checks.py +11 -0
  84. wbcompliance/viewsets/buttons/risk_managment/incidents.py +51 -0
  85. wbcompliance/viewsets/buttons/risk_managment/rules.py +35 -0
  86. wbcompliance/viewsets/compliance_form.py +425 -0
  87. wbcompliance/viewsets/compliance_task.py +513 -0
  88. wbcompliance/viewsets/compliance_type.py +38 -0
  89. wbcompliance/viewsets/display/__init__.py +22 -0
  90. wbcompliance/viewsets/display/compliance_form.py +317 -0
  91. wbcompliance/viewsets/display/compliance_task.py +453 -0
  92. wbcompliance/viewsets/display/compliance_type.py +22 -0
  93. wbcompliance/viewsets/display/risk_managment/__init__.py +11 -0
  94. wbcompliance/viewsets/display/risk_managment/checks.py +46 -0
  95. wbcompliance/viewsets/display/risk_managment/incidents.py +155 -0
  96. wbcompliance/viewsets/display/risk_managment/rules.py +146 -0
  97. wbcompliance/viewsets/display/risk_managment/tables.py +51 -0
  98. wbcompliance/viewsets/endpoints/__init__.py +27 -0
  99. wbcompliance/viewsets/endpoints/compliance_form.py +207 -0
  100. wbcompliance/viewsets/endpoints/compliance_task.py +193 -0
  101. wbcompliance/viewsets/endpoints/compliance_type.py +9 -0
  102. wbcompliance/viewsets/endpoints/risk_managment/__init__.py +12 -0
  103. wbcompliance/viewsets/endpoints/risk_managment/checks.py +16 -0
  104. wbcompliance/viewsets/endpoints/risk_managment/incidents.py +36 -0
  105. wbcompliance/viewsets/endpoints/risk_managment/rules.py +32 -0
  106. wbcompliance/viewsets/endpoints/risk_managment/tables.py +14 -0
  107. wbcompliance/viewsets/menu/__init__.py +17 -0
  108. wbcompliance/viewsets/menu/compliance_form.py +49 -0
  109. wbcompliance/viewsets/menu/compliance_task.py +130 -0
  110. wbcompliance/viewsets/menu/compliance_type.py +17 -0
  111. wbcompliance/viewsets/menu/risk_management.py +56 -0
  112. wbcompliance/viewsets/risk_management/__init__.py +21 -0
  113. wbcompliance/viewsets/risk_management/checks.py +49 -0
  114. wbcompliance/viewsets/risk_management/incidents.py +204 -0
  115. wbcompliance/viewsets/risk_management/mixins.py +52 -0
  116. wbcompliance/viewsets/risk_management/rules.py +179 -0
  117. wbcompliance/viewsets/risk_management/tables.py +96 -0
  118. wbcompliance/viewsets/titles/__init__.py +17 -0
  119. wbcompliance/viewsets/titles/compliance_form.py +101 -0
  120. wbcompliance/viewsets/titles/compliance_task.py +60 -0
  121. wbcompliance/viewsets/titles/compliance_type.py +13 -0
  122. wbcompliance/viewsets/titles/risk_managment/__init__.py +1 -0
  123. wbcompliance/viewsets/titles/risk_managment/checks.py +0 -0
  124. wbcompliance/viewsets/titles/risk_managment/incidents.py +0 -0
  125. wbcompliance/viewsets/titles/risk_managment/rules.py +0 -0
  126. wbcompliance/viewsets/titles/risk_managment/tables.py +7 -0
  127. wbcompliance-2.2.1.dist-info/METADATA +7 -0
  128. wbcompliance-2.2.1.dist-info/RECORD +129 -0
  129. 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