wbcrm 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.

Potentially problematic release.


This version of wbcrm might be problematic. Click here for more details.

Files changed (155) hide show
  1. wbcrm/__init__.py +1 -0
  2. wbcrm/admin/__init__.py +4 -0
  3. wbcrm/admin/accounts.py +59 -0
  4. wbcrm/admin/activities.py +101 -0
  5. wbcrm/admin/groups.py +7 -0
  6. wbcrm/admin/products.py +8 -0
  7. wbcrm/apps.py +5 -0
  8. wbcrm/configurations/__init__.py +1 -0
  9. wbcrm/configurations/base.py +16 -0
  10. wbcrm/dynamic_preferences_registry.py +38 -0
  11. wbcrm/factories/__init__.py +14 -0
  12. wbcrm/factories/accounts.py +56 -0
  13. wbcrm/factories/activities.py +125 -0
  14. wbcrm/factories/groups.py +23 -0
  15. wbcrm/factories/products.py +10 -0
  16. wbcrm/filters/__init__.py +10 -0
  17. wbcrm/filters/accounts.py +67 -0
  18. wbcrm/filters/activities.py +181 -0
  19. wbcrm/filters/groups.py +20 -0
  20. wbcrm/filters/products.py +37 -0
  21. wbcrm/filters/signals.py +94 -0
  22. wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +3948 -0
  23. wbcrm/migrations/0002_alter_activity_repeat_choice.py +32 -0
  24. wbcrm/migrations/0003_remove_activity_external_id_and_more.py +63 -0
  25. wbcrm/migrations/0004_alter_activity_status.py +28 -0
  26. wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +182 -0
  27. wbcrm/migrations/0006_alter_activity_location.py +17 -0
  28. wbcrm/migrations/0007_alter_account_status.py +23 -0
  29. wbcrm/migrations/0008_alter_activity_options.py +16 -0
  30. wbcrm/migrations/0009_alter_account_is_public.py +19 -0
  31. wbcrm/migrations/0010_alter_account_reference_id.py +17 -0
  32. wbcrm/migrations/0011_activity_summary.py +22 -0
  33. wbcrm/migrations/0012_alter_activity_summary.py +17 -0
  34. wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +34 -0
  35. wbcrm/migrations/0014_alter_account_relationship_status.py +24 -0
  36. wbcrm/migrations/0015_alter_activity_type.py +23 -0
  37. wbcrm/migrations/0016_auto_20241205_1015.py +106 -0
  38. wbcrm/migrations/__init__.py +0 -0
  39. wbcrm/models/__init__.py +4 -0
  40. wbcrm/models/accounts.py +637 -0
  41. wbcrm/models/activities.py +1335 -0
  42. wbcrm/models/groups.py +118 -0
  43. wbcrm/models/products.py +83 -0
  44. wbcrm/models/recurrence.py +279 -0
  45. wbcrm/preferences.py +14 -0
  46. wbcrm/serializers/__init__.py +23 -0
  47. wbcrm/serializers/accounts.py +126 -0
  48. wbcrm/serializers/activities.py +526 -0
  49. wbcrm/serializers/groups.py +30 -0
  50. wbcrm/serializers/products.py +57 -0
  51. wbcrm/serializers/recurrence.py +90 -0
  52. wbcrm/serializers/signals.py +70 -0
  53. wbcrm/synchronization/__init__.py +0 -0
  54. wbcrm/synchronization/activity/__init__.py +0 -0
  55. wbcrm/synchronization/activity/admin.py +72 -0
  56. wbcrm/synchronization/activity/backend.py +207 -0
  57. wbcrm/synchronization/activity/backends/__init__.py +0 -0
  58. wbcrm/synchronization/activity/backends/google/__init__.py +2 -0
  59. wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +399 -0
  60. wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +16 -0
  61. wbcrm/synchronization/activity/backends/google/tasks.py +21 -0
  62. wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
  63. wbcrm/synchronization/activity/backends/google/tests/conftest.py +1 -0
  64. wbcrm/synchronization/activity/backends/google/tests/test_data.py +81 -0
  65. wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +319 -0
  66. wbcrm/synchronization/activity/backends/google/tests/test_utils.py +274 -0
  67. wbcrm/synchronization/activity/backends/google/typing_informations.py +139 -0
  68. wbcrm/synchronization/activity/backends/google/utils.py +216 -0
  69. wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
  70. wbcrm/synchronization/activity/backends/outlook/backend.py +576 -0
  71. wbcrm/synchronization/activity/backends/outlook/msgraph.py +438 -0
  72. wbcrm/synchronization/activity/backends/outlook/parser.py +423 -0
  73. wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
  74. wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +1 -0
  75. wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +606 -0
  76. wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +117 -0
  77. wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +269 -0
  78. wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +237 -0
  79. wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +173 -0
  80. wbcrm/synchronization/activity/controller.py +545 -0
  81. wbcrm/synchronization/activity/dynamic_preferences_registry.py +107 -0
  82. wbcrm/synchronization/activity/preferences.py +21 -0
  83. wbcrm/synchronization/activity/shortcuts.py +9 -0
  84. wbcrm/synchronization/activity/signals.py +28 -0
  85. wbcrm/synchronization/activity/tasks.py +21 -0
  86. wbcrm/synchronization/activity/urls.py +6 -0
  87. wbcrm/synchronization/activity/utils.py +46 -0
  88. wbcrm/synchronization/activity/views.py +37 -0
  89. wbcrm/synchronization/admin.py +1 -0
  90. wbcrm/synchronization/apps.py +15 -0
  91. wbcrm/synchronization/dynamic_preferences_registry.py +1 -0
  92. wbcrm/synchronization/management.py +36 -0
  93. wbcrm/synchronization/tasks.py +1 -0
  94. wbcrm/synchronization/urls.py +5 -0
  95. wbcrm/tasks.py +312 -0
  96. wbcrm/tests/__init__.py +0 -0
  97. wbcrm/tests/accounts/__init__.py +0 -0
  98. wbcrm/tests/accounts/test_models.py +380 -0
  99. wbcrm/tests/accounts/test_viewsets.py +87 -0
  100. wbcrm/tests/conftest.py +76 -0
  101. wbcrm/tests/disable_signals.py +52 -0
  102. wbcrm/tests/e2e/__init__.py +1 -0
  103. wbcrm/tests/e2e/e2e_wbcrm_utility.py +82 -0
  104. wbcrm/tests/e2e/test_e2e.py +369 -0
  105. wbcrm/tests/test_assignee_methods.py +39 -0
  106. wbcrm/tests/test_chartviewsets.py +111 -0
  107. wbcrm/tests/test_dto.py +63 -0
  108. wbcrm/tests/test_filters.py +51 -0
  109. wbcrm/tests/test_models.py +216 -0
  110. wbcrm/tests/test_recurrence.py +291 -0
  111. wbcrm/tests/test_report.py +20 -0
  112. wbcrm/tests/test_serializers.py +170 -0
  113. wbcrm/tests/test_tasks.py +94 -0
  114. wbcrm/tests/test_viewsets.py +967 -0
  115. wbcrm/tests/tests.py +120 -0
  116. wbcrm/typings.py +107 -0
  117. wbcrm/urls.py +67 -0
  118. wbcrm/viewsets/__init__.py +22 -0
  119. wbcrm/viewsets/accounts.py +121 -0
  120. wbcrm/viewsets/activities.py +315 -0
  121. wbcrm/viewsets/buttons/__init__.py +7 -0
  122. wbcrm/viewsets/buttons/accounts.py +27 -0
  123. wbcrm/viewsets/buttons/activities.py +68 -0
  124. wbcrm/viewsets/buttons/signals.py +17 -0
  125. wbcrm/viewsets/display/__init__.py +12 -0
  126. wbcrm/viewsets/display/accounts.py +110 -0
  127. wbcrm/viewsets/display/activities.py +443 -0
  128. wbcrm/viewsets/display/groups.py +22 -0
  129. wbcrm/viewsets/display/products.py +105 -0
  130. wbcrm/viewsets/endpoints/__init__.py +8 -0
  131. wbcrm/viewsets/endpoints/accounts.py +32 -0
  132. wbcrm/viewsets/endpoints/activities.py +30 -0
  133. wbcrm/viewsets/endpoints/groups.py +7 -0
  134. wbcrm/viewsets/endpoints/products.py +9 -0
  135. wbcrm/viewsets/groups.py +37 -0
  136. wbcrm/viewsets/menu/__init__.py +8 -0
  137. wbcrm/viewsets/menu/accounts.py +18 -0
  138. wbcrm/viewsets/menu/activities.py +61 -0
  139. wbcrm/viewsets/menu/groups.py +16 -0
  140. wbcrm/viewsets/menu/products.py +20 -0
  141. wbcrm/viewsets/mixins.py +34 -0
  142. wbcrm/viewsets/previews/__init__.py +1 -0
  143. wbcrm/viewsets/previews/activities.py +10 -0
  144. wbcrm/viewsets/products.py +56 -0
  145. wbcrm/viewsets/recurrence.py +26 -0
  146. wbcrm/viewsets/titles/__init__.py +13 -0
  147. wbcrm/viewsets/titles/accounts.py +22 -0
  148. wbcrm/viewsets/titles/activities.py +61 -0
  149. wbcrm/viewsets/titles/products.py +13 -0
  150. wbcrm/viewsets/titles/utils.py +46 -0
  151. wbcrm/workflows/__init__.py +1 -0
  152. wbcrm/workflows/assignee_methods.py +25 -0
  153. wbcrm-2.2.1.dist-info/METADATA +11 -0
  154. wbcrm-2.2.1.dist-info/RECORD +155 -0
  155. wbcrm-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,369 @@
1
+ import datetime
2
+
3
+ import pytest
4
+ from django.utils import timezone
5
+ from psycopg.types.range import DateRange
6
+ from selenium.webdriver.common.action_chains import ActionChains
7
+ from wbcore.contrib.authentication.factories import SuperUserFactory
8
+ from wbcore.contrib.authentication.models import User
9
+ from wbcore.contrib.authentication.tests.e2e import create_main_company_user
10
+ from wbcore.contrib.directory.models import (
11
+ Company,
12
+ EmployerEmployeeRelationship,
13
+ Person,
14
+ )
15
+ from wbcore.contrib.directory.serializers import (
16
+ ClientManagerModelSerializer,
17
+ CompanyModelSerializer,
18
+ PersonModelSerializer,
19
+ )
20
+ from wbcore.contrib.directory.tests.e2e import (
21
+ create_new_cmr_instance,
22
+ create_new_company_instance,
23
+ create_new_person_instance,
24
+ set_up_companies,
25
+ set_up_persons,
26
+ )
27
+ from wbcore.contrib.directory.viewsets.display import CMR_Color
28
+ from wbcore.test import (
29
+ click_button_by_label,
30
+ click_close_all_widgets,
31
+ click_new_button,
32
+ delete_list_entry,
33
+ does_background_color_match,
34
+ edit_list_instance,
35
+ find_element_by_text,
36
+ is_counter_as_expected,
37
+ is_error_visible,
38
+ is_string_not_visible,
39
+ is_tag_not_visible,
40
+ is_tag_visible,
41
+ is_text_visible,
42
+ navigate_to_filter,
43
+ open_menu_item,
44
+ select_async_filter,
45
+ select_column_filter,
46
+ select_filter,
47
+ set_up,
48
+ )
49
+ from wbcrm.factories import ActivityFactory
50
+ from wbcrm.models import Activity
51
+ from wbcrm.serializers import ActivityModelSerializer
52
+ from wbcrm.tests.e2e import create_new_activity_instance, set_up_activities
53
+
54
+ USER_PASSWORD = "User_Password"
55
+
56
+
57
+ @pytest.mark.skip(reason="no way of currently testing this")
58
+ @pytest.mark.django_db
59
+ class TestCRMFilters:
60
+ def test_entry_filter(self, live_server, selenium):
61
+ """
62
+ Testing the "Entry"-Filter.
63
+ """
64
+
65
+ # Creating a test user
66
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
67
+ # Creating three test companies with different last activities.
68
+ companies = set_up_companies()
69
+ now = timezone.now()
70
+ period_a = DateRange(now - datetime.timedelta(days=15, hours=2), now - datetime.timedelta(days=15))
71
+ period_b = DateRange(now - datetime.timedelta(days=60, hours=2), now - datetime.timedelta(days=60))
72
+ ActivityFactory(period=period_a, companies=[companies[0]])
73
+ ActivityFactory(period=period_b, companies=[companies[1], companies[2]])
74
+
75
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
76
+ navigate_to_filter(selenium, "CRM", "Companies")
77
+
78
+ # And the beginning all three companies should be visible, since no filter is set.
79
+ assert is_counter_as_expected(selenium, Company.objects.count())
80
+
81
+ # With no filters selected the there should be a message with the hint "No filter criteria yet"
82
+ assert is_text_visible(selenium, "No filter criteria yet")
83
+
84
+ # Select a "No Activity" - filter. Filtering for all companies, that had no activity in the last month.
85
+ select_filter(selenium, "No Activity:", "Last Month")
86
+
87
+ # The "No filter criteria yet" text should now be gone and instead there should be a "filter-tag" with "Last Month".
88
+ assert is_string_not_visible(selenium, "No filter criteria yet")
89
+ assert is_tag_visible(selenium, "Last Month")
90
+
91
+ # Only two companies should be listed, since one company had an activity withing the last month.
92
+ assert is_text_visible(selenium, "Company B")
93
+ assert is_text_visible(selenium, "Company C")
94
+ assert is_string_not_visible(selenium, "Company A")
95
+ assert is_counter_as_expected(selenium, 2)
96
+
97
+ # Selecting an additional column filter to filter by company type.
98
+ select_column_filter(selenium, "Type", "Type A")
99
+
100
+ # After selecting the additional filter there should also be an additional filter tag corresponding to the filter and the number of companies should be one.
101
+ assert is_tag_visible(selenium, "Last Month")
102
+ assert is_tag_visible(selenium, "Type A")
103
+ assert is_counter_as_expected(selenium, 1)
104
+ assert is_text_visible(selenium, "Company B")
105
+ assert is_string_not_visible(selenium, "Company C")
106
+
107
+ # Resetting the filters
108
+ click_button_by_label(selenium, "Clear all filters")
109
+ assert is_text_visible(selenium, "No filter criteria yet")
110
+ assert is_tag_not_visible(selenium, "Type A")
111
+ assert is_counter_as_expected(selenium, Company.objects.count())
112
+
113
+ # Close all widgets and switch to the person list
114
+ click_close_all_widgets(selenium)
115
+ set_up_persons()
116
+ navigate_to_filter(selenium, "Persons")
117
+
118
+ assert is_counter_as_expected(selenium, Person.objects.count())
119
+ assert is_text_visible(selenium, "No filter criteria yet")
120
+
121
+ # Select a "Specializations" - filter. Filtering for all persons with "Specialization C".
122
+ select_async_filter(selenium, "Specializations:", "Specialization C")
123
+ assert is_tag_visible(selenium, "Specialization C")
124
+ assert is_string_not_visible(selenium, "No filter criteria yet")
125
+
126
+ # Only two persons should be listed
127
+ assert is_string_not_visible(selenium, "Henry Kalb")
128
+ assert is_text_visible(selenium, "Konrad Zuse")
129
+ assert is_text_visible(selenium, "Ada Lovelace")
130
+ filter_count = Person.objects.filter(specializations__title="Specialization C").count()
131
+ assert is_counter_as_expected(selenium, filter_count)
132
+
133
+ # Selecting an additional column filter to filter by person status.
134
+ select_column_filter(selenium, "Status", "Status C")
135
+
136
+ # After selecting the additional filter there should also be an additional filter tag corresponding to the filter and the number of persons should be one.
137
+ assert is_tag_visible(selenium, "Specialization C")
138
+ assert is_tag_visible(selenium, "Status C")
139
+ assert is_string_not_visible(selenium, "Konrad Zuse")
140
+ assert is_text_visible(selenium, "Ada Lovelace")
141
+ filter_count = EmployerEmployeeRelationship.objects.filter(
142
+ employer__in=Company.objects.filter(customer_status__title="Status C"),
143
+ employee__in=Person.objects.filter(specializations__title="Specialization C"),
144
+ primary=True,
145
+ )
146
+ assert is_counter_as_expected(selenium, filter_count)
147
+
148
+ def test_activity_filter(self, live_server, selenium):
149
+ """
150
+ Testing the "Type" and "Status" - Filter
151
+ """
152
+ # Creating a test user and setting up selenium
153
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
154
+ set_up_activities(user.profile)
155
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
156
+
157
+ navigate_to_filter(selenium, "CRM", "Activities")
158
+
159
+ # And the beginning all three activities should be visible, since no filter is set.
160
+ filter_count = Activity.objects.all().count()
161
+ assert is_counter_as_expected(selenium, filter_count)
162
+
163
+ # With no filters selected the there should be a message with the hint "No filter criteria yet"
164
+ assert is_text_visible(selenium, "No filter criteria yet")
165
+
166
+ # Selecting a column filter to filter by activity type.
167
+ select_column_filter(selenium, "Type", "Meeting")
168
+
169
+ assert is_string_not_visible(selenium, "No filter criteria yet")
170
+ assert is_tag_visible(selenium, "Meeting")
171
+
172
+ # After filtering for the type there should be only two activities displayed
173
+ filter_count = Activity.objects.filter(type__title="Meeting").count()
174
+ assert is_counter_as_expected(selenium, filter_count)
175
+ assert is_text_visible(selenium, "Activity A")
176
+ assert is_text_visible(selenium, "Activity B")
177
+ assert is_string_not_visible(selenium, "Activity C")
178
+
179
+ # Select a "Status" - filter. Filtering for all activities, that have the status "Finished".
180
+ select_filter(selenium, "Status:", "Finished")
181
+
182
+ assert is_string_not_visible(selenium, "Activity A")
183
+ assert is_text_visible(selenium, "Activity B")
184
+ filter_count = Activity.objects.filter(type__title="Meeting", status=Activity.Status.FINISHED).count()
185
+ assert is_counter_as_expected(selenium, filter_count)
186
+
187
+
188
+ @pytest.mark.skip(reason="no way of currently testing this")
189
+ @pytest.mark.django_db
190
+ class TestCRMActivities:
191
+ def test_create_edit_delete_activity(self, live_server, selenium):
192
+ """
193
+ Creates, edits and deletes an activity.
194
+ """
195
+ # Creating a test user and setting up selenium
196
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
197
+ actions = ActionChains(selenium, 1000)
198
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
199
+
200
+ # # -----> CREATE <----- #
201
+ activity_a = create_new_activity_instance(selenium, ["title", "type"], "Type A", "Activity A", False)
202
+ open_menu_item(selenium, "Activities", perform_mouse_move=True)
203
+ assert is_text_visible(selenium, activity_a.title)
204
+ assert is_counter_as_expected(selenium, Activity.objects.count())
205
+
206
+ click_new_button(selenium)
207
+ assert is_text_visible(selenium, "New Activity")
208
+
209
+ # Trying to create an activity without filling out anything -> We expect an error to be thrown.
210
+ click_button_by_label(selenium, "Save and close")
211
+ assert is_error_visible(selenium)
212
+
213
+ activity_b = create_new_activity_instance(selenium, ["title", "type"], None, "Activity B")
214
+ assert is_text_visible(selenium, activity_b.title)
215
+ assert is_counter_as_expected(selenium, Activity.objects.count())
216
+
217
+ # -----> Edit <----- #
218
+
219
+ edit_list_instance(selenium, actions, activity_a, ActivityModelSerializer(activity_a), {"title": "A Activity"})
220
+
221
+ assert is_string_not_visible(selenium, "Activity A")
222
+ assert is_text_visible(selenium, "A Activity")
223
+ assert is_counter_as_expected(selenium, Activity.objects.count())
224
+
225
+ # -----> Delete <----- #
226
+ delete_list_entry(selenium, actions, activity_b.title)
227
+ assert is_counter_as_expected(selenium, Activity.objects.count())
228
+ assert is_string_not_visible(selenium, "Activity A")
229
+ assert is_string_not_visible(selenium, "Activity B")
230
+
231
+
232
+ @pytest.mark.skip(reason="no way of currently testing this")
233
+ @pytest.mark.django_db
234
+ class TestCRMEntries:
235
+ def test_create_edit_delete_company(self, live_server, selenium):
236
+ """
237
+ Creating a new company from the company list view. After creating the company it should be displayed in the company list view.
238
+ """
239
+ # Creating a test user
240
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
241
+ actions = ActionChains(selenium, 1000)
242
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
243
+
244
+ # -----> CREATE <----- #
245
+ company_a = create_new_company_instance(
246
+ selenium, ["name", "customer_status"], "Company A", "Test Status", False
247
+ )
248
+ open_menu_item(selenium, "Companies", perform_mouse_move=True)
249
+ assert is_text_visible(selenium, company_a.name)
250
+ assert is_counter_as_expected(selenium, Company.objects.count())
251
+
252
+ click_new_button(selenium)
253
+ assert is_text_visible(selenium, "New Company")
254
+
255
+ # Trying to create an activity without filling out anything -> We expect an error to be thrown.
256
+ click_button_by_label(selenium, "Save and close")
257
+ assert is_error_visible(selenium)
258
+ company_b = create_new_company_instance(selenium, ["name", "customer_status"], "Company B")
259
+
260
+ assert is_text_visible(selenium, company_b.name)
261
+ assert is_counter_as_expected(selenium, Company.objects.count())
262
+
263
+ # -----> Edit <----- #
264
+
265
+ edit_list_instance(selenium, actions, company_a.name, CompanyModelSerializer(company_a), {"name": "A Company"})
266
+ assert is_string_not_visible(selenium, "Company A")
267
+ assert is_text_visible(selenium, "A Company")
268
+ assert is_counter_as_expected(selenium, Company.objects.count())
269
+
270
+ # -----> Delete <----- #
271
+
272
+ delete_list_entry(selenium, actions, company_b.name)
273
+ assert is_counter_as_expected(selenium, Company.objects.count())
274
+ assert is_text_visible(selenium, "A Company")
275
+ assert is_string_not_visible(selenium, "Company A")
276
+ assert is_string_not_visible(selenium, "Company B")
277
+
278
+ def test_create_edit_delete_person(self, live_server, selenium):
279
+ """
280
+ Creating a new person from the person list view. After creating the person it should be displayed in the person list view.
281
+ """
282
+ # Creating a test user
283
+ user: User = SuperUserFactory(plaintext_password=USER_PASSWORD)
284
+ actions = ActionChains(selenium, 1000)
285
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
286
+
287
+ # -----> CREATE <----- #
288
+ person_a = create_new_person_instance(
289
+ selenium, ["first_name", "last_name", "prefix"], "Max", "Mustermann", "Mr.", False
290
+ )
291
+ open_menu_item(selenium, "Persons", perform_mouse_move=True)
292
+ assert is_text_visible(selenium, f"{person_a.first_name} {person_a.last_name}")
293
+ assert is_counter_as_expected(selenium, Person.objects.count())
294
+
295
+ click_new_button(selenium)
296
+ assert is_text_visible(selenium, "New Person")
297
+
298
+ # Trying to create an activity without filling out anything -> We expect an error to be thrown.
299
+ click_button_by_label(selenium, "Save and close")
300
+ assert is_error_visible(selenium)
301
+ person_b = create_new_person_instance(
302
+ selenium, ["first_name", "last_name", "prefix"], "Maike", "Musterfrau", "Mrs.", False
303
+ )
304
+
305
+ assert is_text_visible(selenium, f"{person_b.first_name} {person_b.last_name}")
306
+ assert is_counter_as_expected(selenium, Person.objects.count())
307
+
308
+ # -----> Edit <----- #
309
+
310
+ edit_list_instance(
311
+ selenium,
312
+ actions,
313
+ f"{person_a.first_name} {person_a.last_name}",
314
+ PersonModelSerializer(person_a),
315
+ {"first_name": "Maxissimus"},
316
+ )
317
+ assert is_string_not_visible(selenium, "Max Mustermann")
318
+ assert is_text_visible(selenium, "Maxissimus Mustermann")
319
+ assert is_counter_as_expected(selenium, Person.objects.count())
320
+
321
+ # -----> Delete <----- #
322
+
323
+ delete_list_entry(selenium, actions, f"{person_b.first_name} {person_b.last_name}")
324
+ assert is_counter_as_expected(selenium, Person.objects.count())
325
+ assert is_text_visible(selenium, "Maxissimus Mustermann")
326
+ assert is_string_not_visible(selenium, "Max Mustermann")
327
+ assert is_string_not_visible(selenium, "Maike Mustermann")
328
+
329
+
330
+ @pytest.mark.skip(reason="no way of currently testing this")
331
+ @pytest.mark.django_db
332
+ # This test class is currently bugged because of frontend issues.
333
+ class TestCRMRelationShipManagement:
334
+ def test_create_edit_and_delete_relationship(self, live_server, selenium):
335
+ """
336
+ Creates, edits and deletes a Customer-Employer-Relationship.
337
+ """
338
+ user: User = create_main_company_user(USER_PASSWORD)
339
+ actions = ActionChains(selenium, 1000)
340
+ set_up(selenium, live_server, user.email, USER_PASSWORD)
341
+ set_up_persons()
342
+
343
+ # -----> CREATE <----- #
344
+ cmr = create_new_cmr_instance(selenium, ["client", "relationship_manager", "primary"], True, False)
345
+ open_menu_item(selenium, "Client Manager Relationships", perform_mouse_move=True)
346
+ cmr_element = find_element_by_text(selenium, cmr.client.computed_str)
347
+
348
+ assert is_counter_as_expected(selenium, 1)
349
+ assert is_text_visible(selenium, cmr.client.computed_str)
350
+ assert is_text_visible(selenium, cmr.relationship_manager.computed_str)
351
+ assert does_background_color_match(cmr_element, CMR_Color.DRAFT.value)
352
+
353
+ # # -----> Edit <----- #
354
+
355
+ edit_list_instance(
356
+ selenium,
357
+ actions,
358
+ cmr.client.computed_str,
359
+ ClientManagerModelSerializer(cmr),
360
+ {"relationship_manager": user.profile.computed_str},
361
+ )
362
+ assert is_string_not_visible(selenium, cmr.relationship_manager.computed_str)
363
+ assert is_text_visible(selenium, user.profile.computed_str)
364
+ assert is_counter_as_expected(selenium, Person.objects.count())
365
+
366
+ # # -----> Approve <----- #
367
+ # TODO Implement approve
368
+ # -----> Delete <----- #
369
+ # TODO Implement delete
@@ -0,0 +1,39 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+ from rest_framework.test import APIRequestFactory
5
+ from wbcore.contrib.authentication.factories import UserFactory
6
+ from wbcore.contrib.directory.factories import PersonFactory
7
+ from wbcore.contrib.workflow.factories import (
8
+ ProcessStepFactory,
9
+ RandomChildStepFactory,
10
+ UserStepFactory,
11
+ )
12
+ from wbcrm.workflows.assignee_methods import activity_assignee
13
+
14
+
15
+ @pytest.mark.django_db
16
+ class TestAssigneeMethods:
17
+ api_factory = APIRequestFactory()
18
+
19
+ def test_activity_assignee(self, activity_factory):
20
+ user = UserFactory()
21
+ activity = activity_factory(assigned_to=user.profile)
22
+ process_step = ProcessStepFactory(process__instance=activity)
23
+ assert activity_assignee(process_step) == user
24
+
25
+ @patch("wbcore.contrib.workflow.models.step.Step.set_failed")
26
+ def test_activity_assignee_no_activity(self, mock_failed):
27
+ step = RandomChildStepFactory(exclude_factories=[UserStepFactory])
28
+ process_step = ProcessStepFactory(step=step)
29
+ assert activity_assignee(process_step) is None
30
+ assert mock_failed.call_args.args[0] == process_step
31
+
32
+ @patch("wbcore.contrib.workflow.models.step.Step.set_failed")
33
+ def test_activity_assignee_no_user_account(self, mock_failed, activity_factory):
34
+ assignee = PersonFactory()
35
+ activity = activity_factory(assigned_to=assignee)
36
+ step = RandomChildStepFactory(exclude_factories=[UserStepFactory])
37
+ process_step = ProcessStepFactory(step=step, process__instance=activity)
38
+ assert activity_assignee(process_step) is None
39
+ assert mock_failed.call_args.args[0] == process_step
@@ -0,0 +1,111 @@
1
+ import pytest
2
+ from rest_framework import status
3
+ from rest_framework.test import APIRequestFactory
4
+ from wbcore.test.utils import get_kwargs
5
+ from wbcrm.factories import ActivityFactory
6
+ from wbcrm.viewsets import ActivityChartModelViewSet, ActivityViewSet
7
+
8
+
9
+ @pytest.mark.django_db
10
+ class TestSpecificsChartViewsets:
11
+ @pytest.mark.parametrize(
12
+ "mvs, factory",
13
+ [
14
+ (ActivityChartModelViewSet, ActivityFactory),
15
+ ],
16
+ )
17
+ def test_option_request(self, mvs, factory, superuser):
18
+ request = APIRequestFactory().options("")
19
+ request.user = superuser
20
+ factory()
21
+ kwargs = {"user_id": request.user.id}
22
+ vs = mvs.as_view({"options": "options"})
23
+ response = vs(request, **kwargs)
24
+ assert response.status_code == status.HTTP_200_OK
25
+ assert response.data
26
+
27
+ @pytest.mark.parametrize(
28
+ "mvs, factory",
29
+ [
30
+ (ActivityChartModelViewSet, ActivityFactory),
31
+ ],
32
+ )
33
+ def test_viewsets(self, mvs, factory, superuser):
34
+ request = APIRequestFactory().get("")
35
+ request.user = superuser
36
+ factory()
37
+ kwargs = {"user_id": request.user.id}
38
+ vs = mvs.as_view({"get": "list"})
39
+ response = vs(request, **kwargs)
40
+ assert response.status_code == status.HTTP_200_OK
41
+ assert response.data
42
+
43
+
44
+ @pytest.mark.django_db
45
+ class TestSpecificsInfiniteViewsets:
46
+ @pytest.mark.parametrize(
47
+ "mvs, factory",
48
+ [
49
+ (
50
+ ActivityViewSet,
51
+ ActivityFactory,
52
+ ),
53
+ ],
54
+ )
55
+ def test_option_request(self, mvs, factory, superuser):
56
+ request = APIRequestFactory().options("")
57
+ request.user = superuser
58
+ obj = factory(participants=(request.user.profile,))
59
+ request.GET = request.GET.copy()
60
+ request.GET["date_gte"] = str(obj.start.date())
61
+ kwargs = {}
62
+ mvs.request = request
63
+ vs = mvs.as_view({"options": "options"})
64
+ kwargs = get_kwargs(obj, mvs, request=request)
65
+ response = vs(request, **kwargs).render()
66
+ assert response.status_code == status.HTTP_200_OK
67
+ assert response.data
68
+
69
+ @pytest.mark.parametrize(
70
+ "mvs, factory",
71
+ [
72
+ (
73
+ ActivityViewSet,
74
+ ActivityFactory,
75
+ ),
76
+ ],
77
+ )
78
+ def test_viewsets(self, mvs, factory, superuser):
79
+ request = APIRequestFactory().get("")
80
+ request.user = superuser
81
+ obj = factory(participants=(request.user.profile,))
82
+ request.GET = request.GET.copy()
83
+ request.GET["date_gte"] = str(obj.start.date())
84
+ kwargs = {}
85
+ mvs.request = request
86
+ vs = mvs.as_view({"get": "list"})
87
+ response = vs(request, **kwargs).render()
88
+ assert response.status_code == status.HTTP_200_OK
89
+ assert response.data
90
+
91
+ @pytest.mark.parametrize(
92
+ "mvs, factory",
93
+ [
94
+ (
95
+ ActivityViewSet,
96
+ ActivityFactory,
97
+ ),
98
+ ],
99
+ )
100
+ def test_viewsets_without_date_gte(self, mvs, factory, superuser):
101
+ request = APIRequestFactory().get("")
102
+ request.user = superuser
103
+ factory(participants=(request.user.profile,))
104
+ request.GET = request.GET.copy()
105
+ request.GET["date_gte"] = None
106
+ kwargs = {}
107
+ mvs.request = request
108
+ vs = mvs.as_view({"get": "list"})
109
+ response = vs(request, **kwargs).render()
110
+ assert response.status_code == status.HTTP_200_OK
111
+ assert response.data
@@ -0,0 +1,63 @@
1
+ import pytest
2
+ from wbcore.contrib.directory.typings import Person as PersonDTO
3
+ from wbcrm.typings import Activity as ActivityDTO
4
+ from wbcrm.typings import ParticipantStatus as ParticipantStatusDTO
5
+ from wbcrm.typings import User as UserDTO
6
+
7
+
8
+ @pytest.mark.django_db
9
+ class TestDTO:
10
+ @pytest.mark.parametrize("user_id", [None, 1])
11
+ def test_user_comparison(self, user_id):
12
+ user1 = UserDTO(1, {})
13
+ user2 = UserDTO(id=user_id, metadata={})
14
+ if user1.id == user2.id is not None:
15
+ assert user1 == user2
16
+ else:
17
+ assert user1 != user2
18
+ assert user2 is not None
19
+
20
+ @pytest.mark.parametrize(
21
+ "id, name, email",
22
+ [
23
+ (None, None, None),
24
+ (None, "test", None),
25
+ (None, "test", "test@test.com"),
26
+ (1, None, None),
27
+ (1, "test", None),
28
+ (1, "test", "test@test.com"),
29
+ ],
30
+ )
31
+ def test_participation_status(self, id, name, email):
32
+ person1 = PersonDTO(1, "test", "test", "test@test.com")
33
+ person2 = PersonDTO(id=id, first_name=name, last_name=name, email=email)
34
+
35
+ participant1 = ParticipantStatusDTO(person=person1)
36
+ participant2 = ParticipantStatusDTO(person=person2)
37
+ if (participant1.id == participant2.id is not None) or (
38
+ participant1.person == participant2.person and participant1.activity == participant2.activity
39
+ ):
40
+ assert participant1 == participant2
41
+ else:
42
+ assert participant1 != participant2
43
+ assert participant2 is not None
44
+
45
+ @pytest.mark.parametrize(
46
+ "id, title, email",
47
+ [
48
+ (None, None, None),
49
+ (None, "test", None),
50
+ (None, "test", "test@test.com"),
51
+ (1, None, None),
52
+ (1, "test", None),
53
+ (1, "test", "test@test.com"),
54
+ ],
55
+ )
56
+ def test_activity(self, id, title, email):
57
+ activity1 = ActivityDTO(id=1, title="test", metadata={})
58
+ activity2 = ActivityDTO(id=id, title=title, metadata={})
59
+ if activity1.id == activity2.id is not None:
60
+ assert activity1 == activity2
61
+ else:
62
+ assert activity1 != activity2
63
+ assert activity2 is not None
@@ -0,0 +1,51 @@
1
+ import pytest
2
+ from django.utils import timezone
3
+ from rest_framework.test import APIRequestFactory
4
+ from wbcore.contrib.authentication.factories import UserFactory
5
+ from wbcore.contrib.directory.factories.entries import PersonWithEmployerFactory
6
+ from wbcore.contrib.directory.models import Company, Person
7
+ from wbcrm.filters.signals import choice_noactivity
8
+ from wbcrm.viewsets import ActivityViewSet
9
+
10
+
11
+ @pytest.mark.django_db
12
+ class TestSpecificFiltersActivities:
13
+ def test_filter_clients_of(self, activity_factory, person_factory):
14
+ person1 = person_factory()
15
+ person2 = person_factory()
16
+ activity_factory(participants=(person_factory(relationship_managers=(person1,)),))
17
+ mvs = ActivityViewSet(kwargs={})
18
+ request = APIRequestFactory().get("")
19
+ request.user = UserFactory(is_active=True, is_superuser=True)
20
+ mvs.request = APIRequestFactory().get("")
21
+ qs = ActivityViewSet.get_model().objects.all()
22
+ assert mvs.filterset_class(request=request).filter_clients_of(qs, "", None) == qs
23
+ assert mvs.filterset_class(request=request).filter_clients_of(qs, "", person1).count() == 1
24
+ assert mvs.filterset_class(request=request).filter_clients_of(qs, "", person2).count() == 0
25
+
26
+
27
+ @pytest.mark.django_db
28
+ class TestSpecificFiltersEntries:
29
+ @pytest.mark.parametrize("base_class", [Person, Company])
30
+ def test_choice_noactivity(self, activity_factory, base_class):
31
+ person = PersonWithEmployerFactory()
32
+ company = person.employer.first().employer
33
+ entry_instance = person if base_class == Person else company
34
+ activity = activity_factory(
35
+ creator=person,
36
+ assigned_to=person,
37
+ latest_reviewer=person,
38
+ preceded_by=None,
39
+ participants=(person,),
40
+ companies=(company,),
41
+ )
42
+ number_of_days_since_last_activity = (timezone.now() - activity.period.upper).days + 1
43
+ number_of_days_no_activity = (timezone.now() - activity.period.upper).days - 1
44
+ qs = base_class.objects.all()
45
+ qs_count = qs.count()
46
+ assert choice_noactivity(qs, "", None) == qs
47
+ assert choice_noactivity(qs, "", 0).count() == qs_count - 1
48
+ assert entry_instance not in choice_noactivity(qs, "", 0)
49
+ assert choice_noactivity(qs, "", number_of_days_since_last_activity).count() == qs_count - 1
50
+ assert entry_instance not in choice_noactivity(qs, "", number_of_days_since_last_activity)
51
+ assert choice_noactivity(qs, "", number_of_days_no_activity).count() == qs_count