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,380 @@
1
+ from datetime import date, timedelta
2
+
3
+ import pytest
4
+ from django.contrib.auth import get_user_model
5
+ from django.contrib.auth.models import Permission
6
+ from faker import Faker
7
+ from psycopg.types.range import DateRange
8
+ from wbcore.contrib.directory.models import Entry
9
+ from wbcore.contrib.directory.signals import deactivate_profile
10
+ from wbcrm.models import Account, AccountRole, AccountRoleValidity
11
+
12
+ fake = Faker()
13
+
14
+
15
+ @pytest.mark.django_db
16
+ class TestAccountModel:
17
+ def test_init(self, account):
18
+ assert account.id is not None
19
+
20
+ def test_account_with_owner_create_account_role(self, account_factory, entry_factory):
21
+ customer = entry_factory.create()
22
+ account = account_factory.create(owner=customer)
23
+ assert account.roles.filter(entry=customer, role_type__key="customer").exists()
24
+
25
+ def test_leaf_account_automatically_set_to_terminal(self, account_factory):
26
+ parent_account = account_factory.create()
27
+
28
+ assert parent_account.is_terminal_account is True
29
+ child_account1 = account_factory.create(parent=parent_account, is_terminal_account=False)
30
+ child_account2 = account_factory.create(parent=parent_account, is_terminal_account=False)
31
+
32
+ parent_account.refresh_from_db()
33
+ child_account1.refresh_from_db()
34
+ child_account2.refresh_from_db()
35
+
36
+ assert parent_account.is_terminal_account is False
37
+ assert child_account1.is_terminal_account is True
38
+ assert child_account2.is_terminal_account is True
39
+
40
+ child_account1.delete(no_deletion=False)
41
+ parent_account.refresh_from_db()
42
+ assert parent_account.is_terminal_account is False # because there is still another child
43
+
44
+ child_account2.delete(no_deletion=False)
45
+ parent_account.refresh_from_db()
46
+ assert parent_account.is_terminal_account is True # There is no children left so we expect it to become leaf
47
+
48
+ def test_disabled_parent_account_disabled_children(self, account_factory):
49
+ parent_account = account_factory.create()
50
+ child_account = account_factory.create(parent=parent_account)
51
+ assert child_account.is_active
52
+ assert parent_account.is_active
53
+
54
+ parent_account.is_active = False
55
+ parent_account.save()
56
+
57
+ child_account.refresh_from_db()
58
+ assert not child_account.is_active
59
+
60
+ def test_private_root_account_set_children_as_private(self, account_factory):
61
+ parent_account = account_factory.create(is_public=True)
62
+ child_account = account_factory.create(parent=parent_account, is_public=True)
63
+ parent_account.is_public = False
64
+ parent_account.save()
65
+ child_account.refresh_from_db()
66
+ assert not child_account.is_public
67
+
68
+ def test_can_manage(self, account, user):
69
+ assert not account.can_administrate(user)
70
+
71
+ perm = Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
72
+ user.user_permissions.add(perm)
73
+
74
+ user = get_user_model().objects.get(
75
+ id=user.id
76
+ ) # we refetch user to clear perm cache, doesn't happen with refresh_from_db
77
+ assert account.can_administrate(user)
78
+
79
+ def test_get_inherited_roles_for_account(self, account_factory, account_role_factory):
80
+ parent_account = account_factory.create()
81
+ parent_role = account_role_factory.create(account=parent_account)
82
+ child_account = account_factory.create(parent=parent_account)
83
+ child_role = account_role_factory.create(account=child_account)
84
+ assert set(child_account.get_inherited_roles_for_account()) == {parent_role}
85
+ assert set(child_account.get_inherited_roles_for_account(include_self=True)) == {child_role, parent_role}
86
+
87
+ @pytest.mark.parametrize("validity_date,account__is_public", [(fake.date_object(), False)])
88
+ def test_can_see_account(self, user, account, account_role_factory, validity_date):
89
+ # Check user doesn't see permission on the account by default
90
+ assert not account.can_see_account(user, validity_date)
91
+ role = account_role_factory.create(account=account, entry=Entry.objects.get(id=user.profile.id))
92
+ # with a valid role, they can see it
93
+ assert account.can_see_account(user, validity_date=validity_date)
94
+
95
+ # with an unvalid role, they cannot see it
96
+ role.validity_set.first().timespan = DateRange(date.min, validity_date - timedelta(days=1))
97
+ role.validity_set.first().save()
98
+ assert account.can_see_account(user, validity_date=validity_date)
99
+
100
+ @pytest.mark.parametrize("account__is_public", [True, False])
101
+ def test_can_see_account_with_internal_employee(self, internal_user_factory, account):
102
+ # Check user doesn't see permission on the account by default
103
+
104
+ # with being an internal user, they can see it
105
+ assert account.can_see_account(internal_user_factory.create()) == account.is_public
106
+
107
+ @pytest.mark.parametrize("account__is_public", [True])
108
+ def test_normaluser_cannot_see_account_public_account(self, user, account):
109
+ assert not account.can_see_account(user)
110
+
111
+ def test_get_accounts_for_customer(self, account_factory, company_factory, employer_employee_relationship_factory):
112
+ owner1 = company_factory.create()
113
+ employee1 = employer_employee_relationship_factory.create(employer=owner1).employee
114
+ owner2 = company_factory.create()
115
+ employee2 = employer_employee_relationship_factory.create(employer=owner2).employee
116
+
117
+ parent_account = account_factory.create(owner=owner1)
118
+ other_parent_account = account_factory.create(owner=owner1)
119
+ child_account = account_factory.create(parent=parent_account, owner=owner2)
120
+ # test if all child account also shows in the queryset as well as the direct related account for that owner
121
+ assert set(Account.get_accounts_for_customer(owner1.entry_ptr)) == {
122
+ parent_account,
123
+ other_parent_account,
124
+ child_account,
125
+ }
126
+ assert set(Account.get_accounts_for_customer(employee1.entry_ptr)) == {
127
+ parent_account,
128
+ other_parent_account,
129
+ child_account,
130
+ }
131
+ assert set(Account.get_accounts_for_customer(owner2.entry_ptr)) == {child_account}
132
+ assert set(Account.get_accounts_for_customer(employee2.entry_ptr)) == {child_account}
133
+
134
+ def test_get_managed_accounts_for_entry(self, entry, account_factory, account_role_factory):
135
+ main_role = account_role_factory.create(entry=entry)
136
+ child_account = account_factory.create(parent=main_role.account)
137
+ other_account = account_factory.create() # noqa
138
+ other_role = account_role_factory.create() # noqa
139
+ assert set(Account.get_managed_accounts_for_entry(entry)) == {main_role.account, child_account}
140
+
141
+ def test_handle_user_deactivation(self, account_role_factory, person_factory):
142
+ profile = person_factory.create()
143
+ substitute_profile = person_factory.create()
144
+
145
+ valid_role = account_role_factory.create(entry=Entry.objects.get(id=profile.id))
146
+ unvalid_role = account_role_factory.create(
147
+ entry=Entry.objects.get(id=profile.id), visibility_daterange=DateRange(date.min, date.today())
148
+ )
149
+
150
+ deactivate_profile.send(AccountRole, instance=profile, substitute_profile=substitute_profile)
151
+ valid_role.refresh_from_db()
152
+ unvalid_role.refresh_from_db()
153
+ assert valid_role.validity_set.first().timespan == DateRange(date.min, date.today() - timedelta(days=1))
154
+ assert valid_role.entry.id == profile.id
155
+ assert unvalid_role.entry.id == profile.id
156
+
157
+ new_role = AccountRole.objects.get(entry_id=substitute_profile.id, account=valid_role.account)
158
+ assert new_role.validity_set.first().timespan == DateRange(date.today() - timedelta(days=1), date.max)
159
+
160
+ @pytest.mark.parametrize(
161
+ "validity_date,is_internal_user, is_superuser",
162
+ [
163
+ (fake.date_object(), True, True),
164
+ (fake.date_object(), False, True),
165
+ (fake.date_object(), True, False),
166
+ (fake.date_object(), False, False),
167
+ ],
168
+ )
169
+ def test_filter_for_user(
170
+ self,
171
+ user_factory,
172
+ internal_user_factory,
173
+ account_factory,
174
+ account_role_factory,
175
+ validity_date,
176
+ is_internal_user,
177
+ is_superuser,
178
+ ):
179
+ # We assume role logic are tested in the AccountRole manager unit test
180
+ # so we test here only:
181
+ # - chain filtering
182
+ # - Return account where user has a valid role
183
+ # - and Return public account if user is superuser or internal
184
+
185
+ if is_internal_user:
186
+ user = internal_user_factory.create()
187
+ else:
188
+ user = user_factory.create()
189
+ if is_superuser:
190
+ user.user_permissions.add(
191
+ Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
192
+ )
193
+ profile_entry = Entry.objects.get(id=user.profile.id)
194
+ public_account = account_factory.create(is_public=True)
195
+ private_account = account_factory.create(is_public=False) # noqa
196
+ valid_role = account_role_factory.create(account__is_public=fake.pybool(), entry=profile_entry)
197
+ children_valid_role_account = account_factory.create(
198
+ parent=valid_role.account, is_public=True
199
+ ) # check that entry with role on the parent account can access the public children account
200
+ account_factory.create(
201
+ parent=valid_role.account, is_public=False
202
+ ) # check that entry with role on the parent account cannot access private children account
203
+
204
+ unvalid_role = account_role_factory.create( # noqa
205
+ account__is_public=False,
206
+ entry=profile_entry,
207
+ visibility_daterange=DateRange(date.min, validity_date - timedelta(days=1)),
208
+ )
209
+ user = get_user_model().objects.get(id=user.id)
210
+ if is_superuser:
211
+ assert set(Account.objects.filter_for_user(user, validity_date)) == set(Account.objects.all())
212
+ elif is_internal_user:
213
+ assert set(Account.objects.filter_for_user(user, validity_date)) == {
214
+ public_account,
215
+ valid_role.account,
216
+ children_valid_role_account,
217
+ }
218
+ assert set(Account.objects.filter(id=public_account.id).filter_for_user(user, validity_date)) == {
219
+ public_account
220
+ } # basic test filter chainage
221
+ else:
222
+ assert set(Account.objects.filter_for_user(user, validity_date)) == {
223
+ valid_role.account,
224
+ children_valid_role_account,
225
+ }
226
+
227
+ def test_cannot_merge_account_with_children(self, account_factory):
228
+ """
229
+ Check that we cannot merge an account that still has children
230
+ """
231
+ base_account = account_factory.create()
232
+ merged_account = account_factory.create()
233
+ children_merged_account = account_factory.create(parent=merged_account)
234
+ base_account.merge(merged_account)
235
+ merged_account.refresh_from_db()
236
+ assert merged_account
237
+
238
+ # but can merge the children into the parent account
239
+ merged_account.merge(children_merged_account)
240
+ with pytest.raises(Account.DoesNotExist):
241
+ children_merged_account.refresh_from_db()
242
+
243
+ def test_cannot_merge_account_on_different_trees(self, account_factory):
244
+ """
245
+ Check that we cannot merge an account that still has children
246
+ """
247
+ base_account = account_factory.create(parent=account_factory.create())
248
+ merged_account = account_factory.create(parent=account_factory.create())
249
+ account_factory.create(parent=merged_account)
250
+ base_account.merge(merged_account)
251
+ merged_account.refresh_from_db()
252
+ assert merged_account
253
+
254
+ def test_account_merging(self, account_factory, account_role_factory, user):
255
+ # TODO implemetns for commission
256
+ pivot_date = date(2023, 1, 1)
257
+ base_account = account_factory.create()
258
+ base_account_role = account_role_factory.create(
259
+ account=base_account, visibility_daterange=DateRange(date.min, pivot_date)
260
+ )
261
+ base_account_role_validity = base_account_role.validity_set.first()
262
+
263
+ merged_account = account_factory.create()
264
+ merged_account_role_overlapping = account_role_factory.create(
265
+ account=merged_account, entry=base_account_role.entry, visibility_daterange=DateRange(pivot_date, date.max)
266
+ )
267
+ merged_account_role_overlapping.authorized_hidden_users.add(user)
268
+ merged_account_role_validity = merged_account_role_overlapping.validity_set.first()
269
+
270
+ merged_account_role_different = account_role_factory.create(account=merged_account)
271
+ merged_account_role_different_validity = merged_account_role_different.validity_set.first()
272
+
273
+ base_account.merge(merged_account)
274
+
275
+ # test that the none overlapping role were forwarded to the base account
276
+ assert set(base_account.roles.all()) == {base_account_role, merged_account_role_different}
277
+
278
+ # test that the validity set for the base role got the new validity from the base merged account role
279
+ assert set(base_account_role.validity_set.all()) == {base_account_role_validity, merged_account_role_validity}
280
+
281
+ # test that the authorized user set on the merged account role were forwarded to the base account
282
+ assert set(base_account_role.authorized_hidden_users.all()) == {user}
283
+
284
+ # test that the merged account was deleted
285
+ with pytest.raises(Account.DoesNotExist):
286
+ merged_account.refresh_from_db()
287
+
288
+ # test that the non overlapping role correctly follows the role
289
+ role = AccountRole.objects.get(account=base_account, entry=merged_account_role_different.entry)
290
+ assert set(role.validity_set.all()) == {merged_account_role_different_validity}
291
+
292
+
293
+ @pytest.mark.django_db
294
+ class TestAccountRoleModel:
295
+ def test_init(self, account_role):
296
+ assert account_role.id is not None
297
+ validity = account_role.validity_set.first()
298
+ assert validity
299
+ assert validity.timespan == DateRange(date.min, date.max)
300
+
301
+ def test_hidden_role_set_account_to_private(self, account_factory, account_role_factory):
302
+ public_account = account_factory.create(is_public=True)
303
+ role = account_role_factory.create(account=public_account, is_hidden=True)
304
+ assert not role.account.is_public
305
+
306
+ # def test_filter_for_user(self, ):
307
+ # # here we want to check that the chained filtering works and
308
+ # # - Internal or super user can see their roles + public account roles if there are unhidden + hidden role if they have the right to see them
309
+ # # - Otherwise, can only see direct roles
310
+ # # - Check if the annotation is_currently_valid is there
311
+ @pytest.mark.parametrize(
312
+ "is_superuser,is_internal_user",
313
+ [
314
+ (True, True),
315
+ (True, False),
316
+ (False, True),
317
+ (False, False),
318
+ ],
319
+ )
320
+ def test_filter_related_account_roles_for_internal_user(
321
+ self, user_factory, internal_user_factory, account_role_factory, is_superuser, is_internal_user
322
+ ):
323
+ if is_internal_user:
324
+ user = internal_user_factory.create()
325
+ else:
326
+ user = user_factory.create()
327
+ if is_superuser:
328
+ user.user_permissions.add(
329
+ Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
330
+ )
331
+ role_unhidden_public_account = account_role_factory.create(is_hidden=False, account__is_public=True)
332
+ role_unhidden_private_account = account_role_factory.create(is_hidden=False, account__is_public=False) # noqa
333
+ role_hidden_public_account_without_privilege = account_role_factory.create( # noqa
334
+ is_hidden=True, account__is_public=True
335
+ )
336
+
337
+ role_hidden_private_account_with_privilege = account_role_factory.create(
338
+ is_hidden=True, account__is_public=False, authorized_hidden_users=[user]
339
+ )
340
+ role_direct = account_role_factory.create(
341
+ entry=Entry.objects.get(id=user.profile.id),
342
+ account__is_public=fake.pybool(),
343
+ )
344
+ role_indirect_private_account = account_role_factory.create(account__is_public=False) # noqa
345
+
346
+ if is_superuser:
347
+ assert set(AccountRole.objects.filter_for_user(user)) == set(AccountRole.objects.all())
348
+ elif is_internal_user:
349
+ assert set(AccountRole.objects.filter_for_user(user)) == {
350
+ role_unhidden_public_account,
351
+ role_hidden_private_account_with_privilege,
352
+ role_direct,
353
+ }
354
+ assert set(AccountRole.objects.filter(is_hidden=True).filter_for_user(user)) == {
355
+ role_hidden_private_account_with_privilege
356
+ } # Test that chain filtering works
357
+ else:
358
+ assert set(AccountRole.objects.filter_for_user(user)) == {role_direct}
359
+
360
+
361
+ @pytest.mark.django_db
362
+ class TestAccountRoleValidityModel:
363
+ @pytest.mark.parametrize("validity_date", [fake.date_object()])
364
+ def test_get_role_validity_subquery(self, account_role, validity_date):
365
+ assert AccountRole.objects.count() == 1
366
+ assert (
367
+ AccountRole.objects.annotate(is_valid=AccountRoleValidity.get_role_validity_subquery(validity_date))
368
+ .filter(is_valid=True)
369
+ .count()
370
+ == 1
371
+ )
372
+
373
+ validity = account_role.validity_set.first()
374
+ validity.timespan = DateRange(date.min, validity_date - timedelta(days=1))
375
+ validity.save()
376
+ assert (
377
+ not AccountRole.objects.annotate(is_valid=AccountRoleValidity.get_role_validity_subquery(validity_date))
378
+ .filter(is_valid=True)
379
+ .exists()
380
+ )
@@ -0,0 +1,87 @@
1
+ import pytest
2
+ from faker import Faker
3
+ from rest_framework.exceptions import AuthenticationFailed
4
+ from rest_framework.test import APIRequestFactory
5
+ from wbcore.contrib.authentication.factories.users import UserFactory
6
+ from wbcore.contrib.directory.models import Entry
7
+ from wbcrm.factories.accounts import AccountFactory, AccountRoleFactory
8
+ from wbcrm.models import Account
9
+ from wbcrm.viewsets.accounts import (
10
+ AccountModelViewSet,
11
+ AccountRepresentationViewSet,
12
+ AccountRoleAccountModelViewSet,
13
+ ChildAccountAccountModelViewSet,
14
+ InheritedAccountRoleAccountModelViewSet,
15
+ )
16
+
17
+ fake = Faker()
18
+
19
+
20
+ @pytest.mark.django_db
21
+ class TestAccountViewset:
22
+ @pytest.fixture
23
+ def account_user(self):
24
+ # True, we create a superuser
25
+
26
+ if fake.pybool():
27
+ user = UserFactory.create(is_superuser=True)
28
+ else:
29
+ user = UserFactory.create(is_superuser=True)
30
+ entry = Entry.objects.get(id=user.profile.id)
31
+
32
+ # Create a bunch of account and roles
33
+ public_account = AccountFactory.create(is_public=True)
34
+ AccountRoleFactory.create(account=public_account, entry=entry)
35
+ AccountRoleFactory.create(account=public_account)
36
+ private_account = AccountFactory.create(is_public=False)
37
+ AccountRoleFactory.create(account=private_account, entry=entry)
38
+ return user
39
+
40
+ @pytest.mark.parametrize(
41
+ "viewset_class",
42
+ [
43
+ AccountRepresentationViewSet,
44
+ AccountModelViewSet,
45
+ ],
46
+ )
47
+ def test_ensure_permission_on_account(self, account_user, viewset_class):
48
+ request = APIRequestFactory().get("")
49
+ request.user = account_user
50
+ viewset = viewset_class(request=request)
51
+ assert set(viewset.get_queryset()) == set(Account.objects.filter_for_user(account_user))
52
+
53
+ def test_ensure_permission_for_nested_view(self, account_user, account_factory):
54
+ request = APIRequestFactory().get("")
55
+ request.user = account_user
56
+ parent_account = Account.objects.first()
57
+ account_factory.create(parent=parent_account, is_public=True)
58
+ viewset = ChildAccountAccountModelViewSet(request=request, kwargs={"account_id": parent_account.id})
59
+
60
+ assert set(viewset.get_queryset()) == set(
61
+ Account.objects.filter(parent=parent_account).filter_for_user(account_user)
62
+ )
63
+
64
+ def test_ensure_permission_on_account_role_account(self, user, account_factory, account_role_factory):
65
+ request = APIRequestFactory().get("")
66
+ request.user = user
67
+ parent_account = account_factory.create()
68
+ child_account = account_factory.create(parent=parent_account)
69
+ viewset_account_role = AccountRoleAccountModelViewSet(
70
+ request=request, kwargs={"account_id": parent_account.id}
71
+ )
72
+ viewset_inherited_account_role = InheritedAccountRoleAccountModelViewSet(
73
+ request=request, kwargs={"account_id": child_account.id}
74
+ )
75
+ with pytest.raises((AuthenticationFailed,)):
76
+ viewset_account_role.get_queryset()
77
+ with pytest.raises((AuthenticationFailed,)):
78
+ viewset_inherited_account_role.get_queryset()
79
+
80
+ role = account_role_factory.create(account=parent_account, entry=Entry.objects.get(id=user.profile.id))
81
+ account_role_factory.create(account=parent_account) # noise account role
82
+ assert set(viewset_account_role.get_queryset()) == {role}
83
+
84
+ account_role_factory.create(
85
+ account=child_account, entry=Entry.objects.get(id=user.profile.id)
86
+ ) # noise account role
87
+ assert set(viewset_inherited_account_role.get_queryset()) == {role}
@@ -0,0 +1,76 @@
1
+ from wbcore.tests.conftest import * # isort:skip
2
+ import os
3
+
4
+ from django.db.models.signals import pre_migrate
5
+ from pytest_factoryboy import register
6
+ from wbcore.contrib.agenda.factories import CalendarItemFactory, ConferenceRoomFactory
7
+ from wbcore.contrib.authentication.factories import (
8
+ InternalUserFactory,
9
+ SuperUserFactory,
10
+ UserActivityFactory,
11
+ UserFactory,
12
+ )
13
+ from wbcore.contrib.directory.factories import (
14
+ CompanyFactory,
15
+ EmailContactFactory,
16
+ EmployerEmployeeRelationshipFactory,
17
+ EntryFactory,
18
+ PersonFactory,
19
+ )
20
+ from wbcrm.factories import (
21
+ AccountFactory,
22
+ AccountRoleFactory,
23
+ AccountRoleTypeFactory,
24
+ ActivityCompanyFactory,
25
+ ActivityFactory,
26
+ ActivityParticipantFactory,
27
+ ActivityPersonFactory,
28
+ ActivityTypeFactory,
29
+ GroupFactory,
30
+ ProductFactory,
31
+ RecurringActivityFactory,
32
+ )
33
+
34
+ register(EntryFactory)
35
+ register(InternalUserFactory)
36
+ register(EmailContactFactory)
37
+ register(CompanyFactory)
38
+ register(PersonFactory)
39
+ register(EmployerEmployeeRelationshipFactory)
40
+ register(ActivityFactory)
41
+ register(ActivityTypeFactory)
42
+ register(RecurringActivityFactory)
43
+ register(ActivityCompanyFactory)
44
+ register(ActivityPersonFactory)
45
+ register(ActivityParticipantFactory)
46
+ register(GroupFactory)
47
+ register(ProductFactory)
48
+ register(ConferenceRoomFactory)
49
+ register(AccountFactory)
50
+ register(AccountRoleFactory)
51
+ register(AccountRoleTypeFactory)
52
+ register(CalendarItemFactory)
53
+
54
+ # Authentication
55
+ register(UserFactory)
56
+ register(SuperUserFactory, "superuser")
57
+ register(UserActivityFactory)
58
+
59
+
60
+ @pytest.fixture(autouse=True, scope="session")
61
+ def django_test_environment(django_test_environment):
62
+ from django.apps import apps
63
+
64
+ get_models = apps.get_models
65
+
66
+ for m in [m for m in get_models() if not m._meta.managed]:
67
+ m._meta.managed = True
68
+
69
+
70
+ @pytest.fixture
71
+ def chrome_options(chrome_options):
72
+ # chrome_options.add_argument("--headless=new")
73
+ return chrome_options
74
+
75
+
76
+ pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("geography"))
@@ -0,0 +1,52 @@
1
+ import inspect
2
+ import re
3
+ from collections import defaultdict
4
+
5
+ from django.db.models.signals import *
6
+
7
+
8
+ class DisableSignals(object):
9
+ def __init__(self, disabled_signals=None):
10
+ self.stashed_signals = defaultdict(list)
11
+ self.disabled_signals = disabled_signals or [
12
+ pre_init,
13
+ post_init,
14
+ pre_save,
15
+ post_save,
16
+ pre_delete,
17
+ post_delete,
18
+ pre_migrate,
19
+ post_migrate,
20
+ ]
21
+
22
+ def __enter__(self):
23
+ for signal in self.disabled_signals:
24
+ self.disconnect(signal)
25
+
26
+ def __exit__(self, exc_type, exc_val, exc_tb):
27
+ for signal in list(self.stashed_signals):
28
+ self.reconnect(signal)
29
+
30
+ def disconnect(self, signal):
31
+ self.stashed_signals[signal] = signal.receivers
32
+ signal.receivers = []
33
+
34
+ def reconnect(self, signal):
35
+ signal.receivers = self.stashed_signals.get(signal, [])
36
+ del self.stashed_signals[signal]
37
+
38
+
39
+ # https://stackoverflow.com/questions/55578230/django-how-to-visualize-signals-and-save-overrides
40
+ RECEIVER_MODELS = re.compile(r"sender=(\w+)\W")
41
+
42
+
43
+ class DisableSignalsNotification(DisableSignals):
44
+ def __enter__(self):
45
+ for signal in self.disabled_signals:
46
+ if not isinstance(signal, ModelSignal):
47
+ continue
48
+ for _, receiver in signal.receivers:
49
+ rcode = inspect.getsource(receiver())
50
+ rmodel = RECEIVER_MODELS.findall(rcode)
51
+ if "Notification" in rmodel:
52
+ self.disconnect(signal)
@@ -0,0 +1 @@
1
+ from .e2e_wbcrm_utility import create_new_activity_instance, set_up_activities
@@ -0,0 +1,82 @@
1
+ from selenium.webdriver.remote.webdriver import WebDriver
2
+ from wbcore.contrib.directory.factories import PersonFactory
3
+ from wbcore.contrib.directory.models import Person
4
+ from wbcore.test import (
5
+ click_element_by_path,
6
+ fill_out_form_fields,
7
+ open_create_instance,
8
+ )
9
+ from wbcrm.factories import ActivityFactory, ActivityTypeFactory
10
+ from wbcrm.models import Activity, ActivityType
11
+ from wbcrm.serializers import ActivityModelSerializer
12
+
13
+
14
+ def set_up_activities(creator: Person = None):
15
+ """Sets up three activities for testing purposes with fixed participants, types and titles.
16
+
17
+ Args:
18
+ creator (Person | None, optional): The person that is used as the activities creator. Defaults to None.
19
+ """
20
+ type_a = ActivityTypeFactory(title="Meeting")
21
+ type_b = ActivityTypeFactory(title="Call")
22
+ participant_a = PersonFactory(last_name="Turing")
23
+ participant_b = PersonFactory(last_name="Zuse")
24
+ participant_c = PersonFactory(last_name="Lovelace")
25
+ activity_creator = creator if creator else PersonFactory()
26
+ ActivityFactory(
27
+ title="Activity A",
28
+ type=type_a,
29
+ creator=activity_creator,
30
+ preceded_by=None,
31
+ participants=[participant_a, participant_c],
32
+ status=Activity.Status.PLANNED,
33
+ )
34
+ ActivityFactory(
35
+ title="Activity B",
36
+ type=type_a,
37
+ creator=activity_creator,
38
+ preceded_by=None,
39
+ participants=[participant_a, participant_b],
40
+ status=Activity.Status.FINISHED,
41
+ )
42
+ ActivityFactory(
43
+ title="Activity C",
44
+ type=type_b,
45
+ creator=activity_creator,
46
+ preceded_by=None,
47
+ participants=[participant_b, participant_c],
48
+ status=Activity.Status.PLANNED,
49
+ )
50
+
51
+
52
+ def create_new_activity_instance(
53
+ driver: WebDriver,
54
+ field_list: list[str],
55
+ type_title="",
56
+ activity_title="",
57
+ is_create_instance_open=True,
58
+ ) -> Activity:
59
+ """A function that automatically creates a new activity for selenium e2e-tests.
60
+ After creating the instance this function will close the create-widget.
61
+
62
+ Args:
63
+ driver (WebDriver): The Selenium webdriver.
64
+ field_list (list[str]): List of fields to be filled in the creation mask. The field names must match the names in the ActivitySerializer.
65
+ type_title (str, optional): The title of the activity type. Defaults to "".
66
+ activity_title (str, optional): The title of the activity. Defaults to "".
67
+ is_create_instance_open (bool, optional): Should be true if the create-widget is already open. Defaults to True.
68
+ """
69
+ if not is_create_instance_open:
70
+ open_create_instance(driver, "CRM", "Create Activity")
71
+
72
+ type_title = type_title if type_title else "Test Activity Type"
73
+ activity_title = activity_title if activity_title else "Test Activity"
74
+ if ActivityType.objects.filter(title=activity_title).exists():
75
+ activity_type = ActivityType.objects.get(title=activity_title)
76
+ else:
77
+ activity_type = ActivityTypeFactory(title=type_title)
78
+ activity = ActivityFactory.build(title=activity_title, type=activity_type)
79
+ serializer = ActivityModelSerializer(activity)
80
+ fill_out_form_fields(driver, serializer, field_list, activity)
81
+ click_element_by_path(driver, "//button[@label='Save and close']")
82
+ return activity