practical 0.1.0 → 3.0.0.pre.alpha1
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.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/app/components/practical/views/flash_messages_component.rb +0 -1
- data/app/components/practical/views/form/fallback_errors_section_component.rb +5 -3
- data/app/components/practical/views/form/option_label_component.rb +0 -1
- data/app/components/practical/views/navigation/breadcrumb_item_component.rb +0 -1
- data/app/components/practical/views/navigation/breadcrumbs_component.rb +2 -1
- data/app/{controllers/concerns/practical/auth/passkeys → concerns/practical/auth/passkeys/controllers}/emergency_registrations.rb +2 -2
- data/app/{controllers/concerns/practical/auth/passkeys → concerns/practical/auth/passkeys/controllers}/web_authn_debug_context.rb +1 -1
- data/app/concerns/practical/memberships/controllers/membership_invitations/register_with_passkey.rb +92 -0
- data/app/lib/practical/forms/datatables/base.rb +80 -0
- data/app/lib/practical/loaders/base.rb +44 -0
- data/app/lib/practical/relation_builders/base.rb +35 -0
- data/app/lib/practical/test/shared/attachment/models/attachment/base.rb +123 -0
- data/app/lib/practical/test/shared/attachment/models/attachment/for_organization.rb +39 -0
- data/app/lib/practical/test/shared/attachment/models/organization/has_attachments.rb +12 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/base.rb +9 -6
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/cross_pollination.rb +49 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/passkey_management/base.rb +508 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/reauthentication/base.rb +27 -9
- data/app/lib/practical/test/shared/auth/passkeys/controllers/reauthentication/cross_pollination.rb +19 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_destroy.rb +26 -8
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_signup.rb +3 -2
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/update.rb +55 -19
- data/app/lib/practical/test/shared/auth/passkeys/controllers/sessions/cross_pollination.rb +29 -0
- data/app/lib/practical/test/shared/auth/passkeys/forms/emergency_registration.rb +0 -1
- data/app/lib/practical/test/shared/auth/passkeys/models/{passkey.rb → passkey/base.rb} +1 -1
- data/app/lib/practical/test/shared/auth/passkeys/models/passkey/emergency_registration.rb +23 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/{resource_with_passkeys.rb → resource_with_passkeys/base.rb} +1 -1
- data/app/lib/practical/test/shared/auth/passkeys/models/resource_with_passkeys/emergency_registration.rb +41 -0
- data/app/lib/practical/test/shared/memberships/controllers/membership_invitations/base.rb +165 -0
- data/app/lib/practical/test/shared/memberships/controllers/membership_invitations/register_with_passkey.rb +417 -0
- data/app/lib/practical/test/shared/memberships/controllers/organization/membership.rb +400 -0
- data/app/lib/practical/test/shared/memberships/controllers/organization/membership_invitation.rb +148 -0
- data/app/lib/practical/test/shared/memberships/controllers/user/membership.rb +119 -0
- data/app/lib/practical/test/shared/memberships/controllers/user/membership_invitation.rb +57 -0
- data/app/lib/practical/test/shared/memberships/forms/create_new_user_with_membership_invitation.rb +197 -0
- data/app/lib/practical/test/shared/memberships/forms/organization/membership.rb +162 -0
- data/app/lib/practical/test/shared/memberships/forms/organization/new_membership_invitation.rb +195 -0
- data/app/lib/practical/test/shared/memberships/forms/user/membership.rb +87 -0
- data/app/lib/practical/test/shared/memberships/models/membership/base.rb +45 -0
- data/app/lib/practical/test/shared/memberships/models/membership_invitation/base.rb +85 -0
- data/app/lib/practical/test/shared/memberships/models/membership_invitation/sending.rb +76 -0
- data/app/lib/practical/test/shared/memberships/models/membership_invitation/use_for_and_notify.rb +55 -0
- data/app/lib/practical/test/shared/memberships/models/organization/base.rb +25 -0
- data/app/lib/practical/test/shared/memberships/models/user/base.rb +23 -0
- data/app/lib/practical/test/shared/memberships/policies/organization/base_resource.rb +29 -0
- data/app/lib/practical/test/shared/memberships/policies/organization/membership.rb +103 -0
- data/app/lib/practical/test/shared/memberships/policies/organization/membership_invitation.rb +94 -0
- data/app/lib/practical/test/shared/memberships/policies/organization/resource/inherits.rb +10 -0
- data/app/lib/practical/test/shared/memberships/policies/organization.rb +70 -0
- data/app/lib/practical/test/shared/memberships/policies/user/membership.rb +78 -0
- data/app/lib/practical/test/shared/memberships/policies/user/membership_invitation.rb +31 -0
- data/app/lib/practical/test/shared/models/normalized_email.rb +0 -1
- data/app/lib/practical/test/shared/policies/user/base.rb +14 -0
- data/app/lib/practical/views/error_handling.rb +2 -0
- data/app/lib/practical/views/error_response.rb +27 -0
- data/app/lib/practical/views/form_builders/base.rb +5 -4
- data/app/lib/practical/views/form_builders/collection_option.rb +5 -0
- data/app/lib/practical/views/icon_set.rb +12 -6
- data/config/locales/auth.en.yml +18 -0
- data/config/locales/memberships.en.yml +129 -0
- data/db/seeds/memberships/default.rb +68 -0
- data/db/seeds/moderators/default.rb +36 -0
- data/db/seeds/setup.rb +16 -0
- data/db/seeds/test/cases/membership_invitations.rb +31 -0
- data/db/seeds/users/default.rb +17 -15
- data/lib/generators/practical/test/shared_test/shared_test_generator.rb +2 -0
- data/lib/practical/framework/engine.rb +8 -0
- data/lib/practical/helpers/honeybadger_helper.rb +11 -0
- data/lib/practical/helpers/selector_helper.rb +8 -0
- data/lib/practical/version.rb +1 -1
- data/lib/practical/views/element_helper.rb +2 -0
- data/lib/practical/views/theme_helper.rb +13 -0
- data/lib/practical.rb +4 -1
- data/lib/tasks/practical/utility.rake +20 -0
- metadata +54 -11
- data/lib/tasks/practical/framework_tasks.rake +0 -6
@@ -0,0 +1,400 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Controllers::Organization::Membership
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "index: lists all non-archived-by_user membership types and membership invitations" do
|
8
|
+
user = users.organization_1_owner
|
9
|
+
organization = organizations.organization_1
|
10
|
+
sign_in(user)
|
11
|
+
|
12
|
+
self_archived_user = users.archived_organization_1_manager
|
13
|
+
self_archived_user.memberships.update!(state: :archived_by_user)
|
14
|
+
|
15
|
+
active_membership_user = users.organization_1_manager
|
16
|
+
pending_reacceptance_user = users.works_at_org_1_and_2
|
17
|
+
archived_by_organization_user = users.retired_staff
|
18
|
+
|
19
|
+
membership_invitation = organization.membership_invitations.create!(email: Faker::Internet.email, membership_type: :staff, sender: user)
|
20
|
+
|
21
|
+
pending_reacceptance_user.memberships.find_by!(organization: organization).update!(state: :pending_reacceptance)
|
22
|
+
|
23
|
+
assert_index_policies_applied(organization: organization) do
|
24
|
+
get organization_memberships_url(organization)
|
25
|
+
end
|
26
|
+
|
27
|
+
assert_response :ok
|
28
|
+
assert_dom 'td', text: active_membership_user.name
|
29
|
+
assert_dom 'td', text: pending_reacceptance_user.name
|
30
|
+
assert_dom 'td', text: archived_by_organization_user.name
|
31
|
+
assert_dom 'td', text: membership_invitation.email
|
32
|
+
assert_dom 'td', text: self_archived_user.name, count: 0
|
33
|
+
end
|
34
|
+
|
35
|
+
test "create: creates a new invitation" do
|
36
|
+
user = users.organization_3_owner
|
37
|
+
organization = organizations.organization_3
|
38
|
+
sign_in(user)
|
39
|
+
|
40
|
+
email = Faker::Internet.email
|
41
|
+
|
42
|
+
params = {
|
43
|
+
organization_new_membership_invitation_form: {
|
44
|
+
email: email,
|
45
|
+
membership_type: :staff
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
assert_difference "MembershipInvitation.count", +1 do
|
50
|
+
assert_no_difference "Membership.count" do
|
51
|
+
assert_create_policies_applied(organization: organization) do
|
52
|
+
post organization_memberships_url(organization), params: params, as: :json
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
assert_json_redirected_to(organization_memberships_url(organization))
|
58
|
+
message = I18n.t('organization_memberships.invitation_sent_message', email: email)
|
59
|
+
assert_flash_message(type: :success, message: message, icon_name: 'envelope-dot')
|
60
|
+
end
|
61
|
+
|
62
|
+
test "create: resends a pending invitation" do
|
63
|
+
user = users.organization_3_owner
|
64
|
+
organization = organizations.organization_3
|
65
|
+
sign_in(user)
|
66
|
+
|
67
|
+
email = users.invited_user_1.email
|
68
|
+
previous_invitation = organization.membership_invitations.find_by!(email: email)
|
69
|
+
previous_invitation.update!(last_sent_at: 1.hour.ago)
|
70
|
+
|
71
|
+
params = {
|
72
|
+
organization_new_membership_invitation_form: {
|
73
|
+
email: email,
|
74
|
+
membership_type: :organization_manager
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
assert_no_difference "MembershipInvitation.count" do
|
79
|
+
assert_create_policies_applied(organization: organization) do
|
80
|
+
post organization_memberships_url(organization), params: params, as: :json
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
assert_json_redirected_to(organization_memberships_url(organization))
|
85
|
+
message = I18n.t('organization_memberships.invitation_sent_message', email: email)
|
86
|
+
assert_flash_message(type: :success, message: message, icon_name: 'envelope-dot')
|
87
|
+
end
|
88
|
+
|
89
|
+
test "create: marks a archived_by_organization invitation as pending_reacceptance" do
|
90
|
+
user = users.organization_2_owner
|
91
|
+
organization = organizations.organization_2
|
92
|
+
sign_in(user)
|
93
|
+
|
94
|
+
email = users.organization_1_staff.email
|
95
|
+
previous_membership = organization.memberships.includes(:user).find_by!(user: {email: email})
|
96
|
+
assert_equal true, previous_membership.pending_reacceptance?
|
97
|
+
|
98
|
+
params = {
|
99
|
+
organization_new_membership_invitation_form: {
|
100
|
+
email: email,
|
101
|
+
membership_type: :organization_manager
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
assert_no_difference "MembershipInvitation.count" do
|
106
|
+
assert_create_policies_applied(organization: organization) do
|
107
|
+
post organization_memberships_url(organization), params: params, as: :json
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
assert_json_redirected_to(organization_memberships_url(organization))
|
112
|
+
assert_flash_message(type: :notice, message: I18n.t('organization_memberships.awaiting_reacceptance_message'), icon_name: 'circle-info')
|
113
|
+
end
|
114
|
+
|
115
|
+
test "create: does nothing & returns a flash message if given an email for an active membership" do
|
116
|
+
user = users.organization_1_manager
|
117
|
+
organization = organizations.organization_1
|
118
|
+
sign_in(user)
|
119
|
+
|
120
|
+
email = users.works_at_org_1_and_2.email
|
121
|
+
previous_membership = organization.memberships.includes(:user).find_by!(user: {email: email})
|
122
|
+
assert_equal true, previous_membership.active?
|
123
|
+
|
124
|
+
params = {
|
125
|
+
organization_new_membership_invitation_form: {
|
126
|
+
email: email,
|
127
|
+
membership_type: :organization_manager
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
assert_no_difference "MembershipInvitation.count" do
|
132
|
+
assert_create_policies_applied(organization: organization) do
|
133
|
+
post organization_memberships_url(organization), params: params, as: :json
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
assert_json_redirected_to(organization_memberships_url(organization))
|
138
|
+
assert_flash_message(type: :success, message: I18n.t('organization_memberships.already_member_message'), icon_name: 'circle-check')
|
139
|
+
end
|
140
|
+
|
141
|
+
test "create: renders errors as JSON if given an email for a membership archived_by_user" do
|
142
|
+
user = users.organization_1_manager
|
143
|
+
organization = organizations.organization_1
|
144
|
+
sign_in(user)
|
145
|
+
|
146
|
+
email = users.works_at_org_1_and_2.email
|
147
|
+
previous_membership = organization.memberships.includes(:user).find_by!(user: {email: email})
|
148
|
+
previous_membership.update!(state: :archived_by_user)
|
149
|
+
|
150
|
+
params = {
|
151
|
+
organization_new_membership_invitation_form: {
|
152
|
+
email: email,
|
153
|
+
membership_type: :organization_manager
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
assert_no_difference "MembershipInvitation.count" do
|
158
|
+
assert_create_policies_applied(organization: organization) do
|
159
|
+
post organization_memberships_url(organization), params: params, as: :json
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
assert_response :bad_request
|
164
|
+
|
165
|
+
assert_error_json_contains(
|
166
|
+
container_id: "organization_new_membership_invitation_form_base_errors",
|
167
|
+
element_id: "organization_new_membership_invitation_form_base",
|
168
|
+
message: I18n.t('activemodel.errors.models.organization/new_membership_invitation_form.attributes.base.cannot_be_resent'),
|
169
|
+
type: "cannot_be_resent"
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
test "create: renders errors as JSON if given an email for a non-visible invitation" do
|
174
|
+
user = users.organization_3_owner
|
175
|
+
organization = organizations.organization_3
|
176
|
+
sign_in(user)
|
177
|
+
|
178
|
+
email = users.invited_user_1.email
|
179
|
+
previous_invitation = organization.membership_invitations.find_by!(email: email)
|
180
|
+
previous_invitation.update!(visible: false)
|
181
|
+
|
182
|
+
params = {
|
183
|
+
organization_new_membership_invitation_form: {
|
184
|
+
email: email,
|
185
|
+
membership_type: :organization_manager
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
assert_no_difference "MembershipInvitation.count" do
|
190
|
+
assert_create_policies_applied(organization: organization) do
|
191
|
+
post organization_memberships_url(organization), params: params, as: :json
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
assert_response :bad_request
|
196
|
+
|
197
|
+
assert_error_json_contains(
|
198
|
+
container_id: "organization_new_membership_invitation_form_base_errors",
|
199
|
+
element_id: "organization_new_membership_invitation_form_base",
|
200
|
+
message: I18n.t('activemodel.errors.models.organization/new_membership_invitation_form.attributes.base.cannot_be_resent'),
|
201
|
+
type: "cannot_be_resent"
|
202
|
+
)
|
203
|
+
end
|
204
|
+
|
205
|
+
test "create: renders errors as JSON if given an email for an invitation that was just sent out" do
|
206
|
+
user = users.organization_3_owner
|
207
|
+
organization = organizations.organization_3
|
208
|
+
sign_in(user)
|
209
|
+
|
210
|
+
email = users.invited_user_1.email
|
211
|
+
previous_invitation = organization.membership_invitations.find_by!(email: email)
|
212
|
+
previous_invitation.update!(last_sent_at: Time.now.utc)
|
213
|
+
|
214
|
+
params = {
|
215
|
+
organization_new_membership_invitation_form: {
|
216
|
+
email: email,
|
217
|
+
membership_type: :organization_manager
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
assert_no_difference "MembershipInvitation.count" do
|
222
|
+
assert_create_policies_applied(organization: organization) do
|
223
|
+
post organization_memberships_url(organization), params: params, as: :json
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
assert_response :bad_request
|
228
|
+
|
229
|
+
assert_error_json_contains(
|
230
|
+
container_id: "organization_new_membership_invitation_form_base_errors",
|
231
|
+
element_id: "organization_new_membership_invitation_form_base",
|
232
|
+
message: I18n.t('activemodel.errors.models.organization/new_membership_invitation_form.attributes.base.cannot_be_resent'),
|
233
|
+
type: "cannot_be_resent"
|
234
|
+
)
|
235
|
+
end
|
236
|
+
|
237
|
+
test "create: renders errors as JSON" do
|
238
|
+
user = users.organization_1_manager
|
239
|
+
organization = organizations.organization_1
|
240
|
+
|
241
|
+
sign_in(user)
|
242
|
+
|
243
|
+
params = {
|
244
|
+
organization_new_membership_invitation_form: {
|
245
|
+
email: "",
|
246
|
+
membership_type: :staff
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
assert_no_difference "MembershipInvitation.count" do
|
251
|
+
assert_create_policies_applied(organization: organization) do
|
252
|
+
post organization_memberships_url(organization), params: params, as: :json
|
253
|
+
end
|
254
|
+
end
|
255
|
+
assert_response :bad_request
|
256
|
+
|
257
|
+
assert_error_json_contains(
|
258
|
+
container_id: "organization_new_membership_invitation_form_email_errors",
|
259
|
+
element_id: "organization_new_membership_invitation_form_email",
|
260
|
+
message: "can't be blank",
|
261
|
+
type: "blank"
|
262
|
+
)
|
263
|
+
end
|
264
|
+
|
265
|
+
test "create: renders errors as HTML" do
|
266
|
+
user = users.organization_1_manager
|
267
|
+
organization = organizations.organization_1
|
268
|
+
|
269
|
+
sign_in(user)
|
270
|
+
|
271
|
+
params = {
|
272
|
+
organization_new_membership_invitation_form: {
|
273
|
+
email: "",
|
274
|
+
membership_type: :staff
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
assert_no_difference "MembershipInvitation.count" do
|
279
|
+
assert_create_policies_applied(organization: organization) do
|
280
|
+
post organization_memberships_url(organization), params: params
|
281
|
+
end
|
282
|
+
end
|
283
|
+
assert_response :bad_request
|
284
|
+
|
285
|
+
assert_error_dom(
|
286
|
+
container_id: "organization_new_membership_invitation_form_email_errors",
|
287
|
+
message: "can't be blank"
|
288
|
+
)
|
289
|
+
end
|
290
|
+
|
291
|
+
test "edit: renders the form" do
|
292
|
+
user = users.organization_1_owner
|
293
|
+
organization = organizations.organization_1
|
294
|
+
sign_in(user)
|
295
|
+
|
296
|
+
membership = users.organization_1_manager.memberships.find_by!(organization: organization)
|
297
|
+
|
298
|
+
assert_edit_policies_applied(organization: organization, membership: membership) do
|
299
|
+
get edit_organization_membership_url(organization, membership)
|
300
|
+
end
|
301
|
+
|
302
|
+
assert_response :ok
|
303
|
+
|
304
|
+
css_select "form[action='#{organization_membership_url(organization, membership)}']"
|
305
|
+
end
|
306
|
+
|
307
|
+
test "update: can archive an active membership" do
|
308
|
+
user = users.organization_1_owner
|
309
|
+
organization = organizations.organization_1
|
310
|
+
sign_in(user)
|
311
|
+
|
312
|
+
membership = users.organization_1_manager.memberships.find_by!(organization: organization)
|
313
|
+
|
314
|
+
assert_update_policies_applied(organization: organization, membership: membership) do
|
315
|
+
patch organization_membership_url(organization, membership), params: {organization_membership_form: {
|
316
|
+
state: :archived_by_organization
|
317
|
+
}}
|
318
|
+
end
|
319
|
+
|
320
|
+
assert_flash_message(type: :success, message: I18n.t('organization_memberships.updated_message'), icon_name: 'circle-check')
|
321
|
+
assert_redirected_to edit_organization_membership_url(organization, membership)
|
322
|
+
assert_equal true, membership.reload.archived_by_organization?
|
323
|
+
end
|
324
|
+
|
325
|
+
test "update: can archive a pending_reacceptance membership" do
|
326
|
+
user = users.organization_2_owner
|
327
|
+
organization = organizations.organization_2
|
328
|
+
sign_in(user)
|
329
|
+
|
330
|
+
membership = users.organization_1_staff.memberships.find_by!(organization: organization)
|
331
|
+
|
332
|
+
assert_update_policies_applied(organization: organization, membership: membership) do
|
333
|
+
patch organization_membership_url(organization, membership), params: {organization_membership_form: {
|
334
|
+
state: :archived_by_organization
|
335
|
+
}}
|
336
|
+
end
|
337
|
+
|
338
|
+
assert_redirected_to edit_organization_membership_url(organization, membership)
|
339
|
+
assert_equal true, membership.reload.archived_by_organization?
|
340
|
+
end
|
341
|
+
|
342
|
+
test "update: can unarchive an archived_by_organization membership" do
|
343
|
+
user = users.organization_1_owner
|
344
|
+
organization = organizations.organization_1
|
345
|
+
sign_in(user)
|
346
|
+
|
347
|
+
membership = users.archived_organization_1_manager.memberships.find_by!(organization: organization)
|
348
|
+
|
349
|
+
assert_update_policies_applied(organization: organization, membership: membership) do
|
350
|
+
patch organization_membership_url(organization, membership), params: {organization_membership_form: {
|
351
|
+
state: :pending_reacceptance
|
352
|
+
}}
|
353
|
+
end
|
354
|
+
|
355
|
+
assert_flash_message(type: :success, message: I18n.t('organization_memberships.updated_message'), icon_name: 'circle-check')
|
356
|
+
assert_redirected_to edit_organization_membership_url(organization, membership)
|
357
|
+
assert_equal true, membership.reload.pending_reacceptance?
|
358
|
+
end
|
359
|
+
|
360
|
+
test "update: update the membership_type" do
|
361
|
+
user = users.organization_1_owner
|
362
|
+
organization = organizations.organization_1
|
363
|
+
sign_in(user)
|
364
|
+
|
365
|
+
membership = users.organization_1_manager.memberships.find_by!(organization: organization)
|
366
|
+
|
367
|
+
assert_update_policies_applied(organization: organization, membership: membership) do
|
368
|
+
patch organization_membership_url(organization, membership), params: {organization_membership_form: {
|
369
|
+
membership_type: :staff
|
370
|
+
}}
|
371
|
+
end
|
372
|
+
|
373
|
+
assert_redirected_to edit_organization_membership_url(organization, membership)
|
374
|
+
assert_equal true, membership.reload.staff?
|
375
|
+
end
|
376
|
+
|
377
|
+
test "update: returns an error if trying to archive the only organization_manager" do
|
378
|
+
user = users.organization_2_owner
|
379
|
+
organization = organizations.organization_2
|
380
|
+
sign_in(user)
|
381
|
+
|
382
|
+
membership = user.memberships.find_by!(organization: organization)
|
383
|
+
|
384
|
+
assert_update_policies_applied(organization: organization, membership: membership) do
|
385
|
+
patch organization_membership_url(organization, membership), params: {organization_membership_form: {
|
386
|
+
state: :archived_by_organization
|
387
|
+
}}
|
388
|
+
end
|
389
|
+
|
390
|
+
assert_response :bad_request
|
391
|
+
|
392
|
+
assert_error_dom(
|
393
|
+
container_id: "generic_errors_organization_membership_form",
|
394
|
+
message: /This member cannot be archived/
|
395
|
+
)
|
396
|
+
|
397
|
+
assert_equal true, membership.reload.active?
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
data/app/lib/practical/test/shared/memberships/controllers/organization/membership_invitation.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Controllers::Organization::MembershipInvitation
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "resend: resends a pending invitation and returns a flash message" do
|
8
|
+
user = users.organization_3_owner
|
9
|
+
organization = organizations.organization_3
|
10
|
+
sign_in(user)
|
11
|
+
|
12
|
+
membership_invitation = organization.membership_invitations.find_by!(email: users.invited_user_1.email)
|
13
|
+
time = Time.now.utc
|
14
|
+
|
15
|
+
Timecop.freeze(time) do
|
16
|
+
assert_policies_applied(organization: organization, membership_invitation: membership_invitation) do
|
17
|
+
assert_no_difference "MembershipInvitation.count" do
|
18
|
+
patch resend_organization_membership_invitation_url(organization, membership_invitation)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
assert_redirected_to organization_memberships_url(organization)
|
24
|
+
message = I18n.t('organization_memberships.invitation_sent_message', email: membership_invitation.email)
|
25
|
+
assert_flash_message(type: :success, message: message, icon_name: 'envelope-dot')
|
26
|
+
|
27
|
+
assert_enqueued_email_with(MembershipInvitationMailer, :invitation, args: [{membership_invitation: membership_invitation}])
|
28
|
+
assert_equal time.to_fs(:db), membership_invitation.reload.last_sent_at.to_fs(:db)
|
29
|
+
end
|
30
|
+
|
31
|
+
test "resend: does nothing if the invitation cannot be resent yet" do
|
32
|
+
user = users.organization_3_owner
|
33
|
+
organization = organizations.organization_3
|
34
|
+
sign_in(user)
|
35
|
+
|
36
|
+
membership_invitation = organization.membership_invitations.find_by!(email: users.invited_user_1.email)
|
37
|
+
membership_invitation.update!(last_sent_at: Time.now.utc)
|
38
|
+
old_last_sent_at = membership_invitation.last_sent_at
|
39
|
+
|
40
|
+
assert_policies_applied(organization: organization, membership_invitation: membership_invitation) do
|
41
|
+
assert_no_difference "MembershipInvitation.count" do
|
42
|
+
patch resend_organization_membership_invitation_url(organization, membership_invitation)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
assert_redirected_to organization_memberships_url(organization)
|
47
|
+
message = I18n.t("organization_memberships.cannot_be_resent_message")
|
48
|
+
assert_flash_message(type: :alert, message: message, icon_name: 'triangle-exclamation')
|
49
|
+
|
50
|
+
assert_no_enqueued_emails
|
51
|
+
assert_equal old_last_sent_at.to_fs(:db), membership_invitation.reload.last_sent_at.to_fs(:db)
|
52
|
+
end
|
53
|
+
|
54
|
+
test "resend: returns 404 if the invitation is already tied to a user" do
|
55
|
+
user = users.organization_3_owner
|
56
|
+
organization = organizations.organization_3
|
57
|
+
sign_in(user)
|
58
|
+
|
59
|
+
membership_invitation = users.invited_user_2.membership_invitations.find_by!(organization: organization)
|
60
|
+
old_last_sent_at = membership_invitation.last_sent_at
|
61
|
+
|
62
|
+
assert_policies_applied_on_404(organization: organization) do
|
63
|
+
assert_no_difference "MembershipInvitation.count" do
|
64
|
+
patch resend_organization_membership_invitation_url(organization, membership_invitation)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
assert_response :not_found
|
69
|
+
assert_no_enqueued_emails
|
70
|
+
end
|
71
|
+
|
72
|
+
test "resend: does nothing if the invitation is not visible" do
|
73
|
+
user = users.organization_3_owner
|
74
|
+
organization = organizations.organization_3
|
75
|
+
sign_in(user)
|
76
|
+
|
77
|
+
membership_invitation = organization.membership_invitations.find_by!(email: users.invited_user_1.email)
|
78
|
+
membership_invitation.update!(visible: false)
|
79
|
+
|
80
|
+
assert_policies_applied_on_404(organization: organization) do
|
81
|
+
assert_no_difference "MembershipInvitation.count" do
|
82
|
+
patch resend_organization_membership_invitation_url(organization, membership_invitation)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
assert_response :not_found
|
87
|
+
|
88
|
+
assert_no_enqueued_emails
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
test "destroy: destroys a pending invitation and returns a flash message" do
|
93
|
+
user = users.organization_3_owner
|
94
|
+
organization = organizations.organization_3
|
95
|
+
sign_in(user)
|
96
|
+
|
97
|
+
membership_invitation = organization.membership_invitations.find_by!(email: users.invited_user_1.email)
|
98
|
+
|
99
|
+
assert_policies_applied(organization: organization, membership_invitation: membership_invitation) do
|
100
|
+
assert_difference "MembershipInvitation.count", -1 do
|
101
|
+
delete organization_membership_invitation_url(organization, membership_invitation)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_redirected_to organization_memberships_url(organization)
|
106
|
+
message = I18n.t('organization_memberships.invitation_revoked_message', email: membership_invitation.email)
|
107
|
+
assert_flash_message(type: :alert, message: message, icon_name: 'link-slash')
|
108
|
+
|
109
|
+
assert_no_enqueued_emails
|
110
|
+
end
|
111
|
+
|
112
|
+
test "destroy: returns 404 if the invitation is already tied to a user" do
|
113
|
+
user = users.organization_3_owner
|
114
|
+
organization = organizations.organization_3
|
115
|
+
sign_in(user)
|
116
|
+
|
117
|
+
membership_invitation = users.invited_user_2.membership_invitations.find_by!(organization: organization)
|
118
|
+
old_last_sent_at = membership_invitation.last_sent_at
|
119
|
+
|
120
|
+
assert_policies_applied_on_404(organization: organization) do
|
121
|
+
assert_no_difference "MembershipInvitation.count" do
|
122
|
+
delete organization_membership_invitation_url(organization, membership_invitation)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
assert_response :not_found
|
127
|
+
assert_no_enqueued_emails
|
128
|
+
end
|
129
|
+
|
130
|
+
test "destroy: returns 404 if the invitation is not visible" do
|
131
|
+
user = users.organization_3_owner
|
132
|
+
organization = organizations.organization_3
|
133
|
+
sign_in(user)
|
134
|
+
|
135
|
+
membership_invitation = organization.membership_invitations.find_by!(email: users.invited_user_1.email)
|
136
|
+
membership_invitation.update!(visible: false)
|
137
|
+
|
138
|
+
assert_policies_applied_on_404(organization: organization) do
|
139
|
+
assert_no_difference "MembershipInvitation.count" do
|
140
|
+
delete organization_membership_invitation_url(organization, membership_invitation)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
assert_response :not_found
|
145
|
+
assert_no_enqueued_emails
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Controllers::User::Membership
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "index: lists all non-archived-by_organization membership types and membership invitations" do
|
8
|
+
extra_organization = Organization.create!(name: Faker::Company.name)
|
9
|
+
invitation_organization = Organization.create(name: Faker::Company.name)
|
10
|
+
invitation_organization.memberships.create!(user: users.user_1, membership_type: :organization_manager, state: :active)
|
11
|
+
|
12
|
+
user = users.works_at_org_1_and_2
|
13
|
+
sign_in(user)
|
14
|
+
|
15
|
+
self_archived_membership = user.memberships.find_by(organization: organizations.organization_1)
|
16
|
+
self_archived_membership.update!(state: :archived_by_user)
|
17
|
+
|
18
|
+
active_membership = user.memberships.find_by!(organization: organizations.organization_2)
|
19
|
+
pending_reacceptance_membership = user.memberships.create!(organization: organizations.organization_3, membership_type: :staff, state: :pending_reacceptance)
|
20
|
+
archived_by_organization_membership = user.memberships.create!(organization: extra_organization, membership_type: :staff, state: :archived_by_organization)
|
21
|
+
|
22
|
+
membership_invitation = invitation_organization.membership_invitations.create!(email: user.email, membership_type: :staff, sender: users.user_1)
|
23
|
+
|
24
|
+
assert_index_policies_applied(user: user) do
|
25
|
+
get user_memberships_url
|
26
|
+
end
|
27
|
+
|
28
|
+
assert_response :ok
|
29
|
+
assert_dom 'td', text: active_membership.organization.name
|
30
|
+
assert_dom 'td', text: pending_reacceptance_membership.organization.name
|
31
|
+
assert_dom 'td', text: self_archived_membership.organization.name
|
32
|
+
assert_dom 'td', text: membership_invitation.organization.name
|
33
|
+
assert_dom 'td', text: archived_by_organization_membership.organization.name, count: 0
|
34
|
+
end
|
35
|
+
|
36
|
+
test "update: can archive an active membership" do
|
37
|
+
user = users.works_at_org_1_and_2
|
38
|
+
sign_in(user)
|
39
|
+
|
40
|
+
membership = user.memberships.find_by!(organization: organizations.organization_1)
|
41
|
+
|
42
|
+
assert_update_policies_applied(user: user, membership: membership) do
|
43
|
+
patch user_membership_url(membership), params: {user_membership_form: {
|
44
|
+
state: :archived_by_user
|
45
|
+
}}
|
46
|
+
end
|
47
|
+
|
48
|
+
assert_flash_message(type: :notice, message: I18n.t('user_memberships.archived_message'), icon_name: 'circle-info')
|
49
|
+
assert_redirected_to user_memberships_url
|
50
|
+
assert_equal true, membership.reload.archived_by_user?
|
51
|
+
end
|
52
|
+
|
53
|
+
test "update: can archive a pending_reacceptance membership" do
|
54
|
+
user = users.organization_1_staff
|
55
|
+
sign_in(user)
|
56
|
+
|
57
|
+
membership = user.memberships.find_by!(organization: organizations.organization_2)
|
58
|
+
|
59
|
+
assert_update_policies_applied(user: user, membership: membership) do
|
60
|
+
patch user_membership_url(membership), params: {user_membership_form: {
|
61
|
+
state: :archived_by_user
|
62
|
+
}}
|
63
|
+
end
|
64
|
+
|
65
|
+
assert_flash_message(type: :notice, message: I18n.t('user_memberships.archived_message'), icon_name: 'circle-info')
|
66
|
+
assert_redirected_to user_memberships_url
|
67
|
+
assert_equal true, membership.reload.archived_by_user?
|
68
|
+
end
|
69
|
+
|
70
|
+
test "update: cannot unarchive an archived_by_organization membership" do
|
71
|
+
user = users.retired_staff
|
72
|
+
sign_in(user)
|
73
|
+
|
74
|
+
membership = user.memberships.find_by!(organization: organizations.organization_1)
|
75
|
+
|
76
|
+
assert_not_found_policies_applied(user: user) do
|
77
|
+
patch user_membership_url(membership), params: {user_membership_form: {
|
78
|
+
state: :pending_reacceptance
|
79
|
+
}}
|
80
|
+
end
|
81
|
+
|
82
|
+
assert_response :not_found
|
83
|
+
end
|
84
|
+
|
85
|
+
test "update: cannot update the membership_type" do
|
86
|
+
user = users.organization_1_owner
|
87
|
+
sign_in(user)
|
88
|
+
|
89
|
+
membership = user.memberships.find_by!(organization: organizations.organization_1)
|
90
|
+
|
91
|
+
assert_update_policies_applied(user: user, membership: membership) do
|
92
|
+
patch user_membership_url(membership), params: {user_membership_form: {
|
93
|
+
membership_type: :staff
|
94
|
+
}}
|
95
|
+
end
|
96
|
+
|
97
|
+
assert_redirected_to user_memberships_url
|
98
|
+
assert_equal true, membership.reload.organization_manager?
|
99
|
+
end
|
100
|
+
|
101
|
+
test "update: returns an error if trying to leave the only organization_manager" do
|
102
|
+
user = users.organization_2_owner
|
103
|
+
sign_in(user)
|
104
|
+
|
105
|
+
membership = user.memberships.find_by!(organization: organizations.organization_2)
|
106
|
+
|
107
|
+
assert_update_policies_applied(user: user, membership: membership) do
|
108
|
+
patch user_membership_url(membership), params: {user_membership_form: {
|
109
|
+
state: :archived_by_organization
|
110
|
+
}}
|
111
|
+
end
|
112
|
+
|
113
|
+
assert_flash_message(type: :alert, message: I18n.t('activemodel.errors.models.user/membership_form.attributes.state.inclusion'), icon_name: 'triangle-exclamation')
|
114
|
+
assert_redirected_to user_memberships_url
|
115
|
+
|
116
|
+
assert_equal true, membership.reload.active?
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|