wbcore 2.2.3__py2.py3-none-any.whl → 2.2.5__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 (73) hide show
  1. wbcore/contrib/color/admin.py +28 -0
  2. wbcore/contrib/color/apps.py +5 -0
  3. wbcore/contrib/color/enums.py +17 -0
  4. wbcore/contrib/color/factories.py +10 -0
  5. wbcore/contrib/color/fields.py +29 -0
  6. wbcore/contrib/color/forms.py +13 -0
  7. wbcore/contrib/color/models.py +62 -0
  8. wbcore/contrib/color/tests/conftest.py +10 -0
  9. wbcore/contrib/color/tests/test_color_models.py +61 -0
  10. wbcore/contrib/color/tests/test_fields.py +25 -0
  11. wbcore/contrib/documents/tests/conftest.py +30 -0
  12. wbcore/contrib/documents/tests/test_models.py +144 -0
  13. wbcore/contrib/example_app/tests/test_models/test_event.py +87 -0
  14. wbcore/contrib/example_app/tests/test_models/test_match.py +210 -0
  15. wbcore/contrib/example_app/tests/test_models/test_others.py +159 -0
  16. wbcore/contrib/example_app/tests/test_serializers/test_league_serializer.py +34 -0
  17. wbcore/contrib/example_app/tests/test_serializers/test_match_serializer.py +134 -0
  18. wbcore/contrib/example_app/tests/test_serializers/test_role_serializer.py +13 -0
  19. wbcore/contrib/example_app/tests/test_serializers/test_sport_serializer.py +14 -0
  20. wbcore/contrib/example_app/tests/test_serializers/test_stadium_serializer.py +14 -0
  21. wbcore/contrib/example_app/tests/test_serializers/test_team_result_serializer.py +30 -0
  22. wbcore/contrib/example_app/tests/test_serializers/test_team_serializer.py +70 -0
  23. wbcore/contrib/example_app/tests/test_viewsets/test_event_viewset.py +162 -0
  24. wbcore/contrib/example_app/tests/test_viewsets/test_league_viewset.py +84 -0
  25. wbcore/contrib/example_app/tests/test_viewsets/test_match_viewset.py +65 -0
  26. wbcore/contrib/example_app/tests/test_viewsets/test_person_viewset.py +166 -0
  27. wbcore/contrib/example_app/tests/test_viewsets/test_role_viewset.py +75 -0
  28. wbcore/contrib/example_app/tests/test_viewsets/test_sport_viewset.py +75 -0
  29. wbcore/contrib/example_app/tests/test_viewsets/test_stadium_viewset.py +75 -0
  30. wbcore/contrib/example_app/tests/test_viewsets/test_team_viewset.py +92 -0
  31. wbcore/contrib/example_app/tests/test_viewsets/test_teamresult_viewset.py +58 -0
  32. wbcore/contrib/example_app/tests/test_viewsets/test_utils_viewsets.py +124 -0
  33. wbcore/contrib/guardian/apps.py +6 -0
  34. wbcore/contrib/guardian/configurations.py +3 -0
  35. wbcore/contrib/guardian/filters.py +21 -0
  36. wbcore/contrib/guardian/tasks.py +10 -0
  37. wbcore/contrib/guardian/urls.py +12 -0
  38. wbcore/contrib/guardian/utils.py +124 -0
  39. wbcore/contrib/notifications/viewsets/configs/notification_types.py +27 -0
  40. wbcore/contrib/notifications/viewsets/configs/notifications.py +85 -0
  41. wbcore/contrib/workflow/tests/test_models/step/test_decision_step.py +79 -0
  42. wbcore/contrib/workflow/tests/test_models/step/test_email_step.py +45 -0
  43. wbcore/contrib/workflow/tests/test_models/step/test_finish_step.py +105 -0
  44. wbcore/contrib/workflow/tests/test_models/step/test_join_step.py +127 -0
  45. wbcore/contrib/workflow/tests/test_models/step/test_script_step.py +24 -0
  46. wbcore/contrib/workflow/tests/test_models/step/test_split_step.py +49 -0
  47. wbcore/contrib/workflow/tests/test_models/step/test_step.py +621 -0
  48. wbcore/contrib/workflow/tests/test_models/step/test_user_step.py +225 -0
  49. wbcore/contrib/workflow/tests/test_models/test_condition.py +103 -0
  50. wbcore/contrib/workflow/tests/test_models/test_data.py +134 -0
  51. wbcore/contrib/workflow/tests/test_models/test_process.py +98 -0
  52. wbcore/contrib/workflow/tests/test_models/test_transition.py +128 -0
  53. wbcore/contrib/workflow/tests/test_models/test_workflow.py +358 -0
  54. wbcore/templates/forms.py +0 -0
  55. wbcore/test/e2e_helpers_methods/e2e_checks.py +121 -0
  56. wbcore/test/e2e_helpers_methods/e2e_helper_methods.py +395 -0
  57. wbcore/tests/test_permissions/test_backend.py +29 -0
  58. {wbcore-2.2.3.dist-info → wbcore-2.2.5.dist-info}/METADATA +1 -1
  59. {wbcore-2.2.3.dist-info → wbcore-2.2.5.dist-info}/RECORD +60 -16
  60. wbcore/contrib/agenda/release_notes/1_0_0.md +0 -13
  61. wbcore/contrib/authentication/release_notes/1_0_0.md +0 -13
  62. wbcore/contrib/currency/release_notes/1_0_0.md +0 -13
  63. wbcore/contrib/directory/release_notes/1_0_0.md +0 -13
  64. wbcore/contrib/directory/release_notes/1_0_1.md +0 -13
  65. wbcore/contrib/documents/release_notes/1_0_0.md +0 -13
  66. wbcore/contrib/geography/release_notes/1_0_0.md +0 -13
  67. wbcore/contrib/io/release_notes/1_0_0.md +0 -13
  68. wbcore/contrib/notifications/release_notes/1_0_0.md +0 -13
  69. wbcore/contrib/tags/release_notes/1_0_0.md +0 -13
  70. wbcore/docs/orderable.md +0 -29
  71. wbcore/docs/reparent.md +0 -13
  72. wbcore/templates/reversion/compare_detail.html +0 -19
  73. {wbcore-2.2.3.dist-info → wbcore-2.2.5.dist-info}/WHEEL +0 -0
@@ -0,0 +1,128 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+ from wbcore.contrib.workflow.factories import UserStepFactory
5
+ from wbcore.contrib.workflow.models import Condition
6
+
7
+
8
+ @pytest.mark.django_db
9
+ class TestTransition:
10
+ def test_all_conditions_satisfied_no_conditions(self, transition_factory, process_step_factory):
11
+ transition = transition_factory()
12
+ process_step = process_step_factory()
13
+ assert transition.all_conditions_satisfied(process_step)
14
+
15
+ @patch("wbcore.contrib.workflow.models.step.Step.set_failed")
16
+ @patch("wbcore.contrib.workflow.models.process.ProcessStep.get_instance")
17
+ def test_all_conditions_satisfied_no_instance(
18
+ self,
19
+ mock_get_instance,
20
+ mock_failed,
21
+ random_child_step_factory,
22
+ transition_factory,
23
+ condition_factory,
24
+ process_step_factory,
25
+ ):
26
+ transition = transition_factory()
27
+ condition_factory(transition=transition)
28
+ step = random_child_step_factory(exclude_factories=[UserStepFactory])
29
+ process_step = process_step_factory(step=step)
30
+ mock_get_instance.return_value = None
31
+ assert not transition.all_conditions_satisfied(process_step)
32
+ assert mock_failed.call_args.args[0] == process_step
33
+
34
+ @patch("wbcore.contrib.workflow.models.process.ProcessStep.get_instance")
35
+ @patch("wbcore.contrib.workflow.models.condition.Condition.satisfied")
36
+ def test_all_conditions_satisfied(
37
+ self, mock_satisfied, mock_get_instance, transition_factory, condition_factory, process_step_factory
38
+ ):
39
+ transition = transition_factory()
40
+ process_step = process_step_factory()
41
+ attached_instance = process_step.process.instance
42
+ condition_factory()
43
+ condition_factory(transition=transition)
44
+ condition_factory(transition=transition)
45
+ mock_get_instance.return_value = attached_instance
46
+ mock_satisfied.return_value = True
47
+ assert transition.all_conditions_satisfied(process_step)
48
+ assert mock_satisfied.call_args.args == (attached_instance,)
49
+ assert mock_satisfied.call_count == 2
50
+
51
+ @patch("wbcore.contrib.workflow.models.step.Step.set_failed")
52
+ @patch("wbcore.contrib.workflow.models.process.ProcessStep.get_instance")
53
+ def test_all_conditions_satisfied_errors(
54
+ self,
55
+ mock_get_instance,
56
+ mock_failed,
57
+ random_child_step_factory,
58
+ transition_factory,
59
+ condition_factory,
60
+ process_step_factory,
61
+ ):
62
+ transition = transition_factory()
63
+ step = random_child_step_factory(exclude_factories=[UserStepFactory])
64
+ process_step = process_step_factory(step=step)
65
+ attached_instance = process_step.process.instance
66
+ condition_factory()
67
+ condition_factory(
68
+ transition=transition,
69
+ operator=Condition.Operator.EQ,
70
+ expected_value=attached_instance.first_name,
71
+ negate_operator=False,
72
+ )
73
+ condition_factory(
74
+ transition=transition,
75
+ attribute_name="last_name",
76
+ operator=Condition.Operator.EQ,
77
+ expected_value=attached_instance.last_name,
78
+ negate_operator=False,
79
+ )
80
+ condition_factory(
81
+ transition=transition,
82
+ attribute_name="Test",
83
+ operator=Condition.Operator.EQ,
84
+ expected_value=attached_instance.first_name,
85
+ negate_operator=True,
86
+ )
87
+ mock_get_instance.return_value = attached_instance
88
+ assert not transition.all_conditions_satisfied(process_step)
89
+ assert mock_failed.call_args.args[0] == process_step
90
+
91
+ @patch("wbcore.contrib.workflow.models.step.Step.set_failed")
92
+ @patch("wbcore.contrib.workflow.models.process.ProcessStep.get_instance")
93
+ def test_all_conditions_satisfied_false(
94
+ self,
95
+ mock_get_instance,
96
+ mock_failed,
97
+ random_child_step_factory,
98
+ transition_factory,
99
+ condition_factory,
100
+ process_step_factory,
101
+ ):
102
+ transition = transition_factory()
103
+ step = random_child_step_factory(exclude_factories=[UserStepFactory])
104
+ process_step = process_step_factory(step=step)
105
+ attached_instance = process_step.process.instance
106
+ condition_factory()
107
+ condition_factory(
108
+ transition=transition,
109
+ operator=Condition.Operator.EQ,
110
+ expected_value=attached_instance.first_name,
111
+ negate_operator=False,
112
+ )
113
+ condition_factory(
114
+ transition=transition,
115
+ operator=Condition.Operator.EQ,
116
+ expected_value=attached_instance.first_name,
117
+ negate_operator=True,
118
+ )
119
+ condition_factory(
120
+ transition=transition,
121
+ attribute_name="last_name",
122
+ operator=Condition.Operator.EQ,
123
+ expected_value=attached_instance.last_name,
124
+ negate_operator=False,
125
+ )
126
+ mock_get_instance.return_value = attached_instance
127
+ assert not transition.all_conditions_satisfied(process_step)
128
+ assert not mock_failed.called
@@ -0,0 +1,358 @@
1
+ from unittest.mock import patch
2
+
3
+ import factory
4
+ import pytest
5
+ from django.contrib.contenttypes.models import ContentType
6
+ from wbcore.contrib.authentication.factories import GroupFactory, UserFactory
7
+ from wbcore.contrib.authentication.models import Permission
8
+ from wbcore.contrib.directory.factories import CompanyFactory, PersonFactory
9
+ from wbcore.contrib.directory.models import Person
10
+ from wbcore.contrib.workflow.models import (
11
+ Condition,
12
+ DataValue,
13
+ Process,
14
+ ProcessStep,
15
+ Transition,
16
+ Workflow,
17
+ )
18
+ from wbcore.contrib.workflow.models.workflow import re_render_workflow_graph
19
+ from wbcore.contrib.workflow.sites import workflow_site
20
+ from wbcore.test.utils import get_or_create_superuser
21
+
22
+
23
+ @pytest.mark.django_db
24
+ class TestWorkflow:
25
+ @patch("wbcore.contrib.workflow.models.workflow.transaction.on_commit")
26
+ def test_start_workflow_without_attached_instance(
27
+ self, mock_commit, workflow_factory, data_factory, start_step_factory
28
+ ):
29
+ workflow = workflow_factory(model=None, status_field=None, preserve_instance=False)
30
+ data1 = data_factory(workflow=workflow)
31
+ default_value = "Test"
32
+ data2 = data_factory(workflow=workflow, default=default_value)
33
+ start_step = start_step_factory(workflow=workflow)
34
+ workflow.start_workflow(start_step)
35
+ process_qs = Process.objects.filter(workflow=workflow, instance_id=None, content_type=None)
36
+ assert process_qs.exists()
37
+ assert DataValue.objects.filter(data=data1, process=process_qs.first()).exists()
38
+ assert DataValue.objects.filter(data=data2, process=process_qs.first(), value=default_value).exists()
39
+ assert mock_commit.called
40
+
41
+ @patch("wbcore.contrib.workflow.models.workflow.transaction.on_commit")
42
+ def test_start_workflow_with_attached_instance(
43
+ self, mock_commit, workflow_factory, start_step_factory, data_factory
44
+ ):
45
+ attached_person = PersonFactory()
46
+ workflow = workflow_factory(preserve_instance=True)
47
+ data1 = data_factory(workflow=workflow)
48
+ default_value = "Test"
49
+ data2 = data_factory(workflow=workflow, default=default_value)
50
+ start_step = start_step_factory()
51
+ workflow_site.registered_model_classes_serializer_map[
52
+ Person
53
+ ] = "wbcore.contrib.directory.serializers.PersonModelSerializer"
54
+ workflow.start_workflow(start_step, attached_person)
55
+ process_qs = Process.objects.filter(
56
+ workflow=workflow,
57
+ instance_id=attached_person.pk,
58
+ content_type=ContentType.objects.get_for_model(Person),
59
+ preserved_instance__isnull=False,
60
+ )
61
+ assert process_qs.exists()
62
+ assert DataValue.objects.filter(data=data1, process=process_qs.first()).exists()
63
+ assert DataValue.objects.filter(data=data2, process=process_qs.first(), value=default_value).exists()
64
+ assert mock_commit.called
65
+
66
+ def test_get_start_steps_for_workflow(self, workflow_factory, start_step_factory):
67
+ workflow = workflow_factory(model=None, single_instance_execution=False, status_field=None)
68
+ start_step = start_step_factory(workflow=workflow)
69
+ start_step_factory()
70
+ start_transitions = workflow.get_start_steps_for_workflow()
71
+ assert list(start_transitions) == [start_step]
72
+
73
+ def test_get_start_steps_for_workflow_single_instance_execution(
74
+ self, workflow_factory, process_factory, start_step_factory
75
+ ):
76
+ workflow = workflow_factory(model=None, single_instance_execution=True, status_field=None)
77
+ start_step_factory(workflow=workflow)
78
+ start_step_factory()
79
+ process_factory(state=Process.ProcessState.ACTIVE, workflow=workflow)
80
+ assert not workflow.get_start_steps_for_workflow().exists()
81
+
82
+ def test_get_start_steps_for_workflow_single_instance_execution_no_running_process(
83
+ self, workflow_factory, process_factory, start_step_factory
84
+ ):
85
+ workflow = workflow_factory(model=None, single_instance_execution=True, status_field=None)
86
+ start_step = start_step_factory(workflow=workflow)
87
+ start_step_factory()
88
+ process_factory(state=Process.ProcessState.FINISHED, workflow=workflow)
89
+ start_transitions = workflow.get_start_steps_for_workflow()
90
+ assert list(start_transitions) == [start_step]
91
+
92
+ def test_get_start_steps_for_workflow_with_attached_instance(self, workflow_factory, start_step_factory):
93
+ workflow = workflow_factory()
94
+ start_step_factory(workflow=workflow)
95
+ start_step_factory()
96
+ assert not workflow.get_start_steps_for_workflow().exists()
97
+
98
+ def test_get_start_steps_for_instance(self, workflow_factory, start_step_factory):
99
+ person = PersonFactory()
100
+ workflow = workflow_factory()
101
+ start_step = start_step_factory(workflow=workflow, status=person.first_name)
102
+ start_step_factory(workflow=workflow)
103
+ assert Workflow.get_start_steps_for_instance(person) == [start_step]
104
+
105
+ def test_get_start_steps_for_instance_no_model_attached(self, workflow_factory, start_step_factory):
106
+ person = PersonFactory()
107
+ workflow = workflow_factory(model=None, status_field=None)
108
+ start_step_factory(workflow=workflow, status=person.first_name)
109
+ assert not Workflow.get_start_steps_for_instance(person)
110
+
111
+ def test_get_start_steps_for_instance_wrong_instance(self, workflow_factory, start_step_factory):
112
+ company = CompanyFactory()
113
+ workflow = workflow_factory()
114
+ start_step_factory(workflow=workflow)
115
+ assert not Workflow.get_start_steps_for_instance(company)
116
+
117
+ def test_get_start_steps_for_instance_single_instance_execution(
118
+ self, workflow_factory, process_factory, start_step_factory
119
+ ):
120
+ person = PersonFactory()
121
+ workflow = workflow_factory(single_instance_execution=True)
122
+ start_step_factory(workflow=workflow, status=person.first_name)
123
+ process_factory(finished=None, workflow=workflow, instance=person)
124
+ assert not Workflow.get_start_steps_for_instance(person)
125
+
126
+ def test_get_start_steps_for_instance_single_instance_execution_no_running_process(
127
+ self, workflow_factory, process_factory, start_step_factory
128
+ ):
129
+ attached_instance = PersonFactory()
130
+ workflow = workflow_factory(single_instance_execution=True)
131
+ start_step = start_step_factory(workflow=workflow, status=attached_instance.first_name)
132
+ start_step_factory()
133
+ process_factory(workflow=workflow, instance=attached_instance)
134
+ wrong_instance = CompanyFactory()
135
+ process_factory(workflow=workflow, finished=None, instance=wrong_instance)
136
+ assert Workflow.get_start_steps_for_instance(attached_instance) == [start_step]
137
+
138
+ def test_get_start_steps_for_instance_wrong_status_field(self, workflow_factory, start_step_factory):
139
+ person = PersonFactory()
140
+ workflow = workflow_factory(status_field="Test")
141
+ start_step_factory(workflow=workflow, status=person.first_name)
142
+ assert not Workflow.get_start_steps_for_instance(person)
143
+
144
+ def test_get_start_steps_for_instance_wrong_status(self, workflow_factory, start_step_factory):
145
+ person = PersonFactory()
146
+ workflow = workflow_factory()
147
+ start_step_factory(workflow=workflow, status="Test")
148
+ assert not Workflow.get_start_steps_for_instance(person)
149
+
150
+ def test_get_next_user_step_transitions_for_instance_superuser(
151
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory
152
+ ):
153
+ workflow = workflow_factory()
154
+ step = user_step_factory(workflow=workflow, permission=Permission.objects.last())
155
+ transition = transition_factory(from_step=step, to_step__workflow=workflow)
156
+ process_step = process_step_factory(step=step, state=ProcessStep.StepState.ACTIVE)
157
+ attached_instance = process_step.process.instance
158
+ superuser = get_or_create_superuser()
159
+ start_transitions = Workflow.get_next_user_step_transitions_for_instance(attached_instance, superuser)
160
+ assert start_transitions[0][0] == [transition]
161
+ assert str(start_transitions[0][1].pk) == process_step.pk
162
+
163
+ def test_get_next_user_step_transitions_for_instance_wrong_instance(
164
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory
165
+ ):
166
+ workflow = workflow_factory()
167
+ instance = PersonFactory()
168
+ step = user_step_factory(workflow=workflow, permission=Permission.objects.last())
169
+ transition_factory(from_step=step, to_step__workflow=workflow)
170
+ process_step_factory(step=step, state=ProcessStep.StepState.ACTIVE)
171
+ superuser = get_or_create_superuser()
172
+ assert not Workflow.get_next_user_step_transitions_for_instance(instance, superuser)
173
+
174
+ def test_get_next_user_step_transitions_for_instance_user_is_assignee(
175
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory
176
+ ):
177
+ workflow = workflow_factory()
178
+ step = user_step_factory(workflow=workflow)
179
+ transition = transition_factory(from_step=step, to_step__workflow=workflow)
180
+ user = UserFactory()
181
+ process_step = process_step_factory(step=step, state=ProcessStep.StepState.ACTIVE, assignee=user)
182
+ attached_instance = process_step.process.instance
183
+ start_transitions = Workflow.get_next_user_step_transitions_for_instance(attached_instance, user)
184
+ assert start_transitions[0][0] == [transition]
185
+ assert str(start_transitions[0][1].pk) == process_step.pk
186
+
187
+ def test_get_next_user_step_transitions_for_instance_user_in_assignee_group(
188
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory
189
+ ):
190
+ workflow = workflow_factory()
191
+ step = user_step_factory(workflow=workflow, assignee_method=None)
192
+ transition = transition_factory(from_step=step, to_step__workflow=workflow)
193
+ group = GroupFactory()
194
+ user = UserFactory(groups=[group])
195
+ process_step = process_step_factory(step=step, state=ProcessStep.StepState.ACTIVE, group=group)
196
+ attached_instance = process_step.process.instance
197
+ start_transitions = Workflow.get_next_user_step_transitions_for_instance(attached_instance, user)
198
+ assert start_transitions[0][0] == [transition]
199
+ assert str(start_transitions[0][1].pk) == process_step.pk
200
+
201
+ def test_get_next_user_step_transitions_for_instance_user_in_assignee_group_but_with_method(
202
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory
203
+ ):
204
+ workflow = workflow_factory()
205
+ step = user_step_factory(workflow=workflow, permission=Permission.objects.last())
206
+ transition_factory(from_step=step, to_step__workflow=workflow)
207
+ group = GroupFactory()
208
+ user = UserFactory(groups=[group])
209
+ process_step = process_step_factory(step=step, state=ProcessStep.StepState.ACTIVE, group=group)
210
+ attached_instance = process_step.process.instance
211
+ assert not Workflow.get_next_user_step_transitions_for_instance(attached_instance, user)
212
+
213
+ def test_get_next_user_step_transitions_for_instance_user_has_permission(
214
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory
215
+ ):
216
+ workflow = workflow_factory()
217
+ step = user_step_factory(workflow=workflow, permission=Permission.objects.last())
218
+ transition = transition_factory(from_step=step, to_step__workflow=workflow)
219
+ user = UserFactory()
220
+ user.user_permissions.add(step.permission)
221
+ process_step = process_step_factory(step=step, state=ProcessStep.StepState.ACTIVE, assignee=user)
222
+ attached_instance = process_step.process.instance
223
+ start_transitions = Workflow.get_next_user_step_transitions_for_instance(attached_instance, user)
224
+ assert start_transitions[0][0] == [transition]
225
+ assert str(start_transitions[0][1].pk) == process_step.pk
226
+
227
+ def test_get_next_user_step_transitions_for_instance_user_has_group_permission(
228
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory
229
+ ):
230
+ workflow = workflow_factory()
231
+ step = user_step_factory(workflow=workflow, permission=Permission.objects.last())
232
+ transition = transition_factory(from_step=step, to_step__workflow=workflow)
233
+ group = GroupFactory(permissions=[step.permission])
234
+ user = UserFactory(groups=[group])
235
+ process_step = process_step_factory(step=step, state=ProcessStep.StepState.ACTIVE, assignee=user)
236
+ attached_instance = process_step.process.instance
237
+ start_transitions = Workflow.get_next_user_step_transitions_for_instance(attached_instance, user)
238
+ assert start_transitions[0][0] == [transition]
239
+ assert str(start_transitions[0][1].pk) == process_step.pk
240
+
241
+ def test_get_next_user_step_transitions_for_instance_user_has_no_permission(
242
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory
243
+ ):
244
+ workflow = workflow_factory()
245
+ step = user_step_factory(workflow=workflow, permission=Permission.objects.last())
246
+ transition_factory(from_step=step, to_step__workflow=workflow)
247
+ user = UserFactory()
248
+ process_step = process_step_factory(step=step, state=ProcessStep.StepState.ACTIVE, assignee=user)
249
+ attached_instance = process_step.process.instance
250
+ assert not Workflow.get_next_user_step_transitions_for_instance(attached_instance, user)
251
+
252
+ def test_get_next_user_step_transitions_for_instance_user_no_permission_selected(
253
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory
254
+ ):
255
+ workflow = workflow_factory()
256
+ step = user_step_factory(workflow=workflow)
257
+ transition = transition_factory(from_step=step, to_step__workflow=workflow)
258
+ user = UserFactory()
259
+ process_step = process_step_factory(step=step, state=ProcessStep.StepState.ACTIVE, assignee=user)
260
+ attached_instance = process_step.process.instance
261
+ start_transitions = Workflow.get_next_user_step_transitions_for_instance(attached_instance, user)
262
+ assert start_transitions[0][0] == [transition]
263
+ assert str(start_transitions[0][1].pk) == process_step.pk
264
+
265
+ def test_get_next_user_step_transitions_for_instance_condition_satisfied(
266
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory, condition_factory
267
+ ):
268
+ workflow = workflow_factory()
269
+ person = PersonFactory()
270
+ step = user_step_factory(workflow=workflow)
271
+ transition = transition_factory(from_step=step, to_step__workflow=workflow)
272
+ condition_factory(
273
+ transition=transition,
274
+ attribute_name="first_name",
275
+ expected_value=person.first_name,
276
+ operator=Condition.Operator.EQ,
277
+ negate_operator=False,
278
+ )
279
+ superuser = get_or_create_superuser()
280
+ process_step = process_step_factory(
281
+ step=step, state=ProcessStep.StepState.ACTIVE, assignee=superuser, process__instance=person
282
+ )
283
+ start_transitions = Workflow.get_next_user_step_transitions_for_instance(person, superuser)
284
+ assert start_transitions[0][0] == [transition]
285
+ assert str(start_transitions[0][1].pk) == process_step.pk
286
+
287
+ def test_get_next_user_step_transitions_for_instance_condition_not_satisfied(
288
+ self, workflow_factory, transition_factory, process_step_factory, user_step_factory, condition_factory
289
+ ):
290
+ workflow = workflow_factory()
291
+ person = PersonFactory()
292
+ step = user_step_factory(workflow=workflow)
293
+ transition = transition_factory(from_step=step, to_step__workflow=workflow)
294
+ condition_factory(
295
+ transition=transition,
296
+ attribute_name="first_name",
297
+ expected_value=person.first_name,
298
+ operator=Condition.Operator.EQ,
299
+ negate_operator=True,
300
+ )
301
+ superuser = get_or_create_superuser()
302
+ process_step_factory(
303
+ step=step, state=ProcessStep.StepState.ACTIVE, assignee=superuser, process__instance=person
304
+ )
305
+ assert not Workflow.get_next_user_step_transitions_for_instance(person, superuser)
306
+
307
+ @patch("wbcore.contrib.workflow.models.workflow.Workflow.generate_workflow_png")
308
+ def test_re_render_workflow_graph_workflow(self, mock_generate, workflow_factory):
309
+ workflow = workflow_factory()
310
+ old_graph = workflow.graph
311
+ new_graph = factory.django.ImageField()._make_data({"width": 1024, "height": 768})
312
+ mock_generate.return_value = new_graph
313
+ re_render_workflow_graph(sender=Workflow, instance=workflow, raw=False)
314
+ assert mock_generate.call_count == 1
315
+ assert workflow.graph != old_graph
316
+
317
+ @patch("wbcore.contrib.workflow.models.workflow.Workflow.generate_workflow_png")
318
+ def test_re_render_workflow_graph_step(self, mock_generate, random_child_step_factory):
319
+ step = random_child_step_factory()
320
+ old_graph = step.workflow.graph
321
+ new_graph = factory.django.ImageField()._make_data({"width": 1024, "height": 768})
322
+ mock_generate.return_value = new_graph
323
+ re_render_workflow_graph(sender=step.__class__, instance=step, raw=False)
324
+ # Not really sure why its called twice but as long as we have no infinite loop I'm fine
325
+ assert mock_generate.call_count == 2
326
+ assert step.workflow.graph != old_graph
327
+
328
+ @patch("wbcore.contrib.workflow.models.workflow.Workflow.generate_workflow_png")
329
+ def test_re_render_workflow_graph_transition(self, mock_generate, transition_factory):
330
+ transition = transition_factory()
331
+ old_graph = transition.to_step.workflow.graph
332
+ new_graph = factory.django.ImageField()._make_data({"width": 1024, "height": 768})
333
+ mock_generate.return_value = new_graph
334
+ re_render_workflow_graph(sender=Transition, instance=transition, raw=False)
335
+ # Not really sure why its called twice but as long as we have no infinite loop I'm fine
336
+ assert mock_generate.call_count == 2
337
+ assert transition.to_step.workflow.graph != old_graph
338
+
339
+ @patch("wbcore.contrib.workflow.models.workflow.Workflow.generate_workflow_png")
340
+ def test_re_render_workflow_graph_condition(self, mock_generate, condition_factory):
341
+ condition = condition_factory()
342
+ old_graph = condition.transition.to_step.workflow.graph
343
+ new_graph = factory.django.ImageField()._make_data({"width": 1024, "height": 768})
344
+ mock_generate.return_value = new_graph
345
+ re_render_workflow_graph(sender=Condition, instance=condition, raw=False)
346
+ # Not really sure why its called twice but as long as we have no infinite loop I'm fine
347
+ assert mock_generate.call_count == 2
348
+ assert condition.transition.to_step.workflow.graph != old_graph
349
+
350
+ @patch("wbcore.contrib.workflow.models.workflow.Workflow.generate_workflow_png")
351
+ def test_re_render_workflow_graph_sender_renamed(self, mock_generate, workflow_factory):
352
+ workflow = workflow_factory()
353
+ old_graph = workflow.graph
354
+ new_graph = factory.django.ImageField()._make_data({"width": 1024, "height": 768})
355
+ mock_generate.return_value = new_graph
356
+ re_render_workflow_graph(sender=ProcessStep, instance=workflow, raw=False)
357
+ assert mock_generate.call_count == 0
358
+ assert workflow.graph == old_graph
File without changes
@@ -0,0 +1,121 @@
1
+ from selenium.common.exceptions import TimeoutException
2
+ from selenium.webdriver.common.by import By
3
+ from selenium.webdriver.remote.webdriver import WebDriver
4
+ from selenium.webdriver.remote.webelement import WebElement
5
+ from selenium.webdriver.support import expected_conditions as EC
6
+ from selenium.webdriver.support.color import Color
7
+ from selenium.webdriver.support.wait import WebDriverWait
8
+
9
+ from .e2e_helper_methods import find_element
10
+
11
+
12
+ def does_background_color_match(element: WebElement, expected_color: str) -> bool:
13
+ """Check if the background color of an element matches the expected color.
14
+
15
+ Args:
16
+ element (WebElement): The element to check the background of.
17
+ expected_color (str): The expected color.
18
+
19
+ Returns:
20
+ bool: True if the background color is as expected
21
+ """
22
+ element_rgb = element.value_of_css_property("background-color")
23
+ element_rgb = Color.from_string(element_rgb).rgb
24
+ return element_rgb == expected_color
25
+
26
+
27
+ def is_text_visible(driver: WebDriver, text: str) -> bool:
28
+ """Check if the searched text is visible on the current page.
29
+
30
+ Args:
31
+ driver (WebDriver): The Selenium webdriver.
32
+ text (str): Searched text.
33
+
34
+ Returns:
35
+ bool: True if the text is visible
36
+ """
37
+ return True if find_element(driver, f"//*[text()='{text}']") else False
38
+
39
+
40
+ def is_string_not_visible(driver: WebDriver, string: str) -> bool:
41
+ """Check if the searched text is not visible on the current page.
42
+ Sometimes selenium is faster, that the page can refresh. So this method can wait up to 10 seconds for a string to not be visible.
43
+
44
+ Args:
45
+ driver (WebDriver): The Selenium webdriver.
46
+ text (str): Searched text.
47
+
48
+ Returns:
49
+ bool: True if the text is not visible
50
+ """
51
+ try:
52
+ WebDriverWait(driver, 5).until(EC.invisibility_of_element_located((By.XPATH, f"//*[text()='{string}']")))
53
+ return True
54
+ except TimeoutException:
55
+ return False
56
+
57
+
58
+ def is_tag_visible(driver: WebDriver, tag_label: str) -> bool:
59
+ """Check if a tag with a certain label is visible on the current page.
60
+
61
+ Args:
62
+ driver (WebDriver): The Selenium webdriver.
63
+ text (str): The label text.
64
+
65
+
66
+ Returns:
67
+ bool: True if the label is visible
68
+ """
69
+ return True if find_element(driver, f"//*[@class='tag-label' and text()='{tag_label}']") else False
70
+
71
+
72
+ def is_tag_not_visible(driver: WebDriver, tag_label: str) -> bool:
73
+ """Check if a tag with a certain label is not visible on the current page.
74
+ Sometimes selenium is faster, that the page can refresh. So this method can wait up to 10 seconds for a tag to not be visible.
75
+
76
+ Args:
77
+ driver (WebDriver): The Selenium webdriver.
78
+ text (str): The label text.
79
+
80
+ Returns:
81
+ bool: True if the tag is not visible
82
+ """
83
+ try:
84
+ WebDriverWait(driver, 2.5).until(
85
+ EC.invisibility_of_element_located((By.XPATH, f"//*[@class='tag-label' and text()='{tag_label}']"))
86
+ )
87
+ return True
88
+ except TimeoutException:
89
+ return False
90
+
91
+
92
+ def is_error_visible(driver: WebDriver):
93
+ """Check if an error element is visible on the current page.
94
+
95
+ Args:
96
+ driver (WebDriver): The Selenium webdriver.
97
+
98
+ Returns:
99
+ bool: True if an error element is visible
100
+ """
101
+ error_element = find_element(driver, "//div[contains(@type, 'error')]", 2.5)
102
+ saving_failed_hint = find_element(driver, "//div[contains(@class, 'task-dropper-content')]", 2.5)
103
+
104
+ if saving_failed_hint and saving_failed_hint.is_displayed():
105
+ WebDriverWait(driver, 10).until_not(
106
+ EC.invisibility_of_element_located((By.XPATH, "//div[contains(@class, 'task-dropper-content')]"))
107
+ )
108
+ return error_element is not None
109
+
110
+
111
+ def is_counter_as_expected(driver: WebDriver, count: int):
112
+ """Check if list counter displays the right number.
113
+
114
+ Args:
115
+ driver (WebDriver): The Selenium webdriver.
116
+
117
+ Returns:
118
+ bool: True if the counter is right.
119
+ """
120
+ find_element(driver, f"//span[@class='ag-status-name-value-value' and text()='{count}']")
121
+ return find_element is not None