practical 0.1.0 → 3.0.0.pre.alpha2
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/error_list_component.rb +1 -1
- data/app/components/practical/views/form/error_list_item_component.rb +2 -2
- data/app/components/practical/views/form/error_list_item_template_component.rb +1 -1
- data/app/components/practical/views/form/fallback_errors_section_component.rb +6 -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 +9 -1
- 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 :unprocessable_entity
|
|
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 :unprocessable_entity
|
|
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 :unprocessable_entity
|
|
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 :unprocessable_entity
|
|
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 :unprocessable_entity
|
|
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 :unprocessable_entity
|
|
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
|