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,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Controllers::User::MembershipInvitation
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "destroy: destroys a pending invitation and returns a flash message" do
|
8
|
+
resource = resource_instance
|
9
|
+
sign_in_as_resource(resource: resource)
|
10
|
+
|
11
|
+
membership_invitation = membership_invitation_class.find_by!(email: resource.email)
|
12
|
+
|
13
|
+
assert_policies_applied(resource: resource, membership_invitation: membership_invitation) do
|
14
|
+
assert_difference "#{membership_invitation_class}.count", -1 do
|
15
|
+
delete_invitation_action(membership_invitation: membership_invitation)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
assert_redirected_to user_memberships_url
|
20
|
+
message = I18n.t('user_memberships.invitation_hidden_message', organization_name: membership_invitation.organization.name)
|
21
|
+
assert_flash_message(type: :alert, message: message, icon_name: 'solid-warehouse-slash')
|
22
|
+
end
|
23
|
+
|
24
|
+
test "destroy: returns 404 if the invitation is already tied to a user" do
|
25
|
+
resource = other_resource_instance
|
26
|
+
sign_in_as_resource(resource: resource)
|
27
|
+
|
28
|
+
membership_invitation = membership_invitation_class.find_by!(email: resource.email)
|
29
|
+
|
30
|
+
assert_policies_applied_on_404(resource: resource) do
|
31
|
+
assert_no_difference "membership_invitation_class.count" do
|
32
|
+
delete_invitation_action(membership_invitation: membership_invitation)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
assert_response :not_found
|
37
|
+
assert_no_enqueued_emails
|
38
|
+
end
|
39
|
+
|
40
|
+
test "destroy: returns 404 if the invitation is not visible" do
|
41
|
+
resource = resource_instance
|
42
|
+
sign_in_as_resource(resource: resource)
|
43
|
+
|
44
|
+
membership_invitation = membership_invitation_class.find_by!(email: resource.email)
|
45
|
+
membership_invitation.update!(visible: false)
|
46
|
+
|
47
|
+
assert_policies_applied_on_404(resource: resource) do
|
48
|
+
assert_no_difference "membership_invitation_class.count" do
|
49
|
+
delete_invitation_action(membership_invitation: membership_invitation)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
assert_response :not_found
|
54
|
+
assert_no_enqueued_emails
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/app/lib/practical/test/shared/memberships/forms/create_new_user_with_membership_invitation.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Forms::CreateNewUserWithMembershipInvitation
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
def dummy_webauthn_credential
|
8
|
+
OpenStruct.new(public_key: SecureRandom.hex, raw_id: SecureRandom.bytes(10), sign_count: 0)
|
9
|
+
end
|
10
|
+
|
11
|
+
test "save: merges errors from the attempted new user and re-raises" do
|
12
|
+
membership_invitation = organizations.organization_3.membership_invitations.first
|
13
|
+
form = form_class.new(
|
14
|
+
user: user_class.new(webauthn_id: SecureRandom.hex),
|
15
|
+
membership_invitation: membership_invitation,
|
16
|
+
email: membership_invitation.email,
|
17
|
+
name: " ",
|
18
|
+
passkey_label: Faker::Computer.os,
|
19
|
+
webauthn_credential: dummy_webauthn_credential
|
20
|
+
)
|
21
|
+
|
22
|
+
assert_no_difference "#{user_class}.count" do
|
23
|
+
assert_no_difference "#{passkey_class}.count" do
|
24
|
+
assert_raises ActiveRecord::RecordInvalid do
|
25
|
+
form.save!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
assert_equal true, form.errors.of_kind?(:name, :blank)
|
31
|
+
end
|
32
|
+
|
33
|
+
test "save: merges errors when trying to create a user with an existing email" do
|
34
|
+
membership_invitation = organizations.organization_3.membership_invitations.first
|
35
|
+
form = form_class.new(
|
36
|
+
user: user_class.new(webauthn_id: SecureRandom.hex),
|
37
|
+
membership_invitation: membership_invitation,
|
38
|
+
email: users.organization_1_manager.email,
|
39
|
+
name: Faker::Name.name,
|
40
|
+
passkey_label: Faker::Computer.os,
|
41
|
+
webauthn_credential: dummy_webauthn_credential
|
42
|
+
)
|
43
|
+
|
44
|
+
assert_no_difference "#{user_class}.count" do
|
45
|
+
assert_no_difference "#{passkey_class}.count" do
|
46
|
+
assert_raises ActiveRecord::RecordInvalid do
|
47
|
+
form.save!
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_equal true, form.errors.of_kind?(:email, :taken)
|
53
|
+
end
|
54
|
+
|
55
|
+
test "save: merges errors from the attempted new passkey and re-raises" do
|
56
|
+
membership_invitation = organizations.organization_3.membership_invitations.first
|
57
|
+
|
58
|
+
credential = dummy_webauthn_credential
|
59
|
+
credential.public_key = " "
|
60
|
+
|
61
|
+
form = form_class.new(
|
62
|
+
user: user_class.new(webauthn_id: SecureRandom.hex),
|
63
|
+
membership_invitation: membership_invitation,
|
64
|
+
email: membership_invitation.email,
|
65
|
+
name: Faker::Name.name,
|
66
|
+
passkey_label: " ",
|
67
|
+
webauthn_credential: credential
|
68
|
+
)
|
69
|
+
|
70
|
+
assert_no_difference "#{user_class}.count" do
|
71
|
+
assert_no_difference "#{passkey_class}.count" do
|
72
|
+
assert_raises ActiveRecord::RecordInvalid do
|
73
|
+
form.save!
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
assert_equal true, form.errors.of_kind?(:passkey_label, :blank)
|
79
|
+
assert_equal true, form.errors.of_kind?(:public_key, :blank)
|
80
|
+
end
|
81
|
+
test "save: merges errors when trying to create a passkey that already exists" do
|
82
|
+
membership_invitation = organizations.organization_3.membership_invitations.first
|
83
|
+
|
84
|
+
credential = dummy_webauthn_credential
|
85
|
+
credential.public_key = passkey_class.first.public_key
|
86
|
+
|
87
|
+
form = form_class.new(
|
88
|
+
user: user_class.new(webauthn_id: SecureRandom.hex),
|
89
|
+
membership_invitation: membership_invitation,
|
90
|
+
email: membership_invitation.email,
|
91
|
+
name: Faker::Name.name,
|
92
|
+
passkey_label: Faker::Computer.os,
|
93
|
+
webauthn_credential: credential
|
94
|
+
)
|
95
|
+
|
96
|
+
assert_no_difference "#{user_class}.count" do
|
97
|
+
assert_no_difference "#{passkey_class}.count" do
|
98
|
+
assert_raises ActiveRecord::RecordInvalid do
|
99
|
+
form.save!
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
assert_equal true, form.errors.of_kind?(:public_key, :taken)
|
105
|
+
end
|
106
|
+
|
107
|
+
test "save: raises unexpected errors" do
|
108
|
+
membership_invitation = organizations.organization_3.membership_invitations.first
|
109
|
+
|
110
|
+
Spy.on(membership_invitation, :use_for_and_notify!).and_raise(ArgumentError)
|
111
|
+
|
112
|
+
form = form_class.new(
|
113
|
+
user: user_class.new(webauthn_id: SecureRandom.hex),
|
114
|
+
membership_invitation: membership_invitation,
|
115
|
+
email: membership_invitation.email,
|
116
|
+
name: Faker::Name.name,
|
117
|
+
passkey_label: Faker::Computer.os,
|
118
|
+
webauthn_credential: dummy_webauthn_credential
|
119
|
+
)
|
120
|
+
|
121
|
+
assert_no_difference "#{user_class}.count" do
|
122
|
+
assert_no_difference "#{passkey_class}.count" do
|
123
|
+
assert_raises ArgumentError do
|
124
|
+
form.save!
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
test "save: raises an AlreadyUsedError if the membership_invitation has a user" do
|
131
|
+
membership_invitation = users.invited_user_2.membership_invitations.first
|
132
|
+
|
133
|
+
form = form_class.new(
|
134
|
+
user: user_class.new(webauthn_id: SecureRandom.hex),
|
135
|
+
membership_invitation: membership_invitation,
|
136
|
+
email: Faker::Internet.email,
|
137
|
+
name: Faker::Name.name,
|
138
|
+
passkey_label: Faker::Computer.os,
|
139
|
+
webauthn_credential: dummy_webauthn_credential
|
140
|
+
)
|
141
|
+
|
142
|
+
assert_no_difference "#{user_class}.count" do
|
143
|
+
assert_no_difference "#{passkey_class}.count" do
|
144
|
+
assert_raises MembershipInvitation::AlreadyUsedError do
|
145
|
+
form.save!
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
test """save:
|
152
|
+
- creates the user with the given email & name
|
153
|
+
- creates the passkey for the user
|
154
|
+
- enqueues the email that the membership_invitation has been accepted
|
155
|
+
""" do
|
156
|
+
membership_invitation = organizations.organization_3.membership_invitations.first
|
157
|
+
|
158
|
+
credential = dummy_webauthn_credential
|
159
|
+
|
160
|
+
form = form_class.new(
|
161
|
+
user: user_class.new(webauthn_id: SecureRandom.hex),
|
162
|
+
membership_invitation: membership_invitation,
|
163
|
+
email: membership_invitation.email,
|
164
|
+
name: Faker::Name.name,
|
165
|
+
passkey_label: Faker::Computer.os,
|
166
|
+
webauthn_credential: credential
|
167
|
+
)
|
168
|
+
|
169
|
+
time = Time.now.utc
|
170
|
+
|
171
|
+
Timecop.freeze(time) do
|
172
|
+
assert_difference "#{user_class}.count", +1 do
|
173
|
+
assert_difference "#{passkey_class}.count", +1 do
|
174
|
+
form.save!
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
membership_invitation.reload
|
180
|
+
|
181
|
+
new_user = form.user
|
182
|
+
|
183
|
+
assert_equal true, new_user.persisted?
|
184
|
+
assert_equal form.email, new_user.email
|
185
|
+
assert_equal form.name, new_user.name
|
186
|
+
|
187
|
+
assert_equal new_user, membership_invitation.user
|
188
|
+
|
189
|
+
new_passkey = form.user.passkeys.first
|
190
|
+
|
191
|
+
assert_equal true, new_passkey.persisted?
|
192
|
+
assert_equal form.passkey_label, new_passkey.label
|
193
|
+
assert_equal credential.public_key, new_passkey.public_key
|
194
|
+
assert_equal time.to_formatted_s(:db), new_passkey.created_at.to_formatted_s(:db)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Forms::Organization::Membership
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "raises a validation error if trying to archive the only organization_manager" do
|
8
|
+
user = users.organization_2_owner
|
9
|
+
organization = organizations.organization_2
|
10
|
+
membership = user.memberships.find_by!(organization: organization)
|
11
|
+
|
12
|
+
form = form_class.new(current_user: user,
|
13
|
+
current_organization: organization,
|
14
|
+
membership: membership,
|
15
|
+
state: :archived_by_organization
|
16
|
+
)
|
17
|
+
|
18
|
+
assert_raises ActiveModel::ValidationError do
|
19
|
+
form.save!
|
20
|
+
end
|
21
|
+
|
22
|
+
assert_equal true, form.errors.of_kind?(:state, :cannot_be_archived)
|
23
|
+
assert_equal true, membership.reload.active?
|
24
|
+
end
|
25
|
+
|
26
|
+
test "can archive an organization_manager" do
|
27
|
+
user = users.organization_1_owner
|
28
|
+
organization = organizations.organization_1
|
29
|
+
membership = users.organization_1_manager.memberships.find_by!(organization: organization)
|
30
|
+
|
31
|
+
form = form_class.new(current_user: user,
|
32
|
+
current_organization: organization,
|
33
|
+
membership: membership,
|
34
|
+
state: :archived_by_organization
|
35
|
+
)
|
36
|
+
|
37
|
+
form.save!
|
38
|
+
|
39
|
+
assert_equal true, membership.reload.archived_by_organization?
|
40
|
+
end
|
41
|
+
|
42
|
+
test "can change an archived_by_organization membership to pending_reacceptance" do
|
43
|
+
user = users.organization_1_owner
|
44
|
+
organization = organizations.organization_1
|
45
|
+
membership = users.retired_staff.memberships.find_by!(organization: organization)
|
46
|
+
|
47
|
+
form = form_class.new(current_user: user,
|
48
|
+
current_organization: organization,
|
49
|
+
membership: membership,
|
50
|
+
state: :pending_reacceptance
|
51
|
+
)
|
52
|
+
|
53
|
+
form.save!
|
54
|
+
|
55
|
+
assert_equal true, membership.reload.pending_reacceptance?
|
56
|
+
end
|
57
|
+
|
58
|
+
test "can change a pending_reacceptance membership to archived_by_organization" do
|
59
|
+
user = users.organization_2_owner
|
60
|
+
organization = organizations.organization_2
|
61
|
+
membership = users.organization_1_staff.memberships.find_by!(organization: organization)
|
62
|
+
|
63
|
+
form = form_class.new(current_user: user,
|
64
|
+
current_organization: organization,
|
65
|
+
membership: membership,
|
66
|
+
state: :archived_by_organization
|
67
|
+
)
|
68
|
+
|
69
|
+
form.save!
|
70
|
+
|
71
|
+
assert_equal true, membership.reload.archived_by_organization?
|
72
|
+
end
|
73
|
+
|
74
|
+
test "cannot change the membership_type for the only organization_manager" do
|
75
|
+
user = users.organization_2_owner
|
76
|
+
organization = organizations.organization_2
|
77
|
+
membership = user.memberships.find_by!(organization: organization)
|
78
|
+
|
79
|
+
form = form_class.new(current_user: user,
|
80
|
+
current_organization: organization,
|
81
|
+
membership: membership,
|
82
|
+
membership_type: :staff
|
83
|
+
)
|
84
|
+
|
85
|
+
assert_raises ActiveModel::ValidationError do
|
86
|
+
form.save!
|
87
|
+
end
|
88
|
+
|
89
|
+
assert_equal true, form.errors.of_kind?(:membership_type, :cannot_be_downgraded)
|
90
|
+
assert_equal true, membership.reload.active?
|
91
|
+
assert_equal true, membership.reload.organization_manager?
|
92
|
+
end
|
93
|
+
|
94
|
+
test "can change the membership_type for an organization_manager" do
|
95
|
+
user = users.organization_1_owner
|
96
|
+
organization = organizations.organization_1
|
97
|
+
membership = users.organization_1_manager.memberships.find_by!(organization: organization)
|
98
|
+
|
99
|
+
form = form_class.new(current_user: user,
|
100
|
+
current_organization: organization,
|
101
|
+
membership: membership,
|
102
|
+
membership_type: :staff
|
103
|
+
)
|
104
|
+
|
105
|
+
form.save!
|
106
|
+
|
107
|
+
assert_equal true, membership.reload.active?
|
108
|
+
assert_equal true, membership.reload.staff?
|
109
|
+
end
|
110
|
+
|
111
|
+
test "can change the membership_type for a staff member" do
|
112
|
+
user = users.organization_1_owner
|
113
|
+
organization = organizations.organization_1
|
114
|
+
membership = users.works_at_org_1_and_2.memberships.find_by!(organization: organization)
|
115
|
+
|
116
|
+
form = form_class.new(current_user: user,
|
117
|
+
current_organization: organization,
|
118
|
+
membership: membership,
|
119
|
+
membership_type: :organization_manager
|
120
|
+
)
|
121
|
+
|
122
|
+
form.save!
|
123
|
+
|
124
|
+
assert_equal true, membership.reload.active?
|
125
|
+
assert_equal true, membership.reload.organization_manager?
|
126
|
+
end
|
127
|
+
|
128
|
+
test "can change the membership_type for an archived membership" do
|
129
|
+
user = users.organization_1_owner
|
130
|
+
organization = organizations.organization_1
|
131
|
+
membership = users.retired_staff.memberships.find_by!(organization: organization)
|
132
|
+
|
133
|
+
form = form_class.new(current_user: user,
|
134
|
+
current_organization: organization,
|
135
|
+
membership: membership,
|
136
|
+
membership_type: :organization_manager
|
137
|
+
)
|
138
|
+
|
139
|
+
form.save!
|
140
|
+
|
141
|
+
assert_equal true, membership.reload.archived_by_organization?
|
142
|
+
assert_equal true, membership.reload.organization_manager?
|
143
|
+
end
|
144
|
+
|
145
|
+
test "can change the membership_type for a pending_reacceptance membership" do
|
146
|
+
user = users.organization_2_owner
|
147
|
+
organization = organizations.organization_2
|
148
|
+
membership = users.organization_1_staff.memberships.find_by!(organization: organization)
|
149
|
+
|
150
|
+
form = form_class.new(current_user: user,
|
151
|
+
current_organization: organization,
|
152
|
+
membership: membership,
|
153
|
+
membership_type: :organization_manager
|
154
|
+
)
|
155
|
+
|
156
|
+
form.save!
|
157
|
+
|
158
|
+
assert_equal true, membership.reload.pending_reacceptance?
|
159
|
+
assert_equal true, membership.reload.organization_manager?
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/app/lib/practical/test/shared/memberships/forms/organization/new_membership_invitation.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Forms::Organization::NewMembershipInvitation
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "save!: raises a validation error if the email is blank" do
|
8
|
+
form = form_class.new(email: " ")
|
9
|
+
assert_raises ActiveModel::ValidationError do
|
10
|
+
form.save!
|
11
|
+
end
|
12
|
+
|
13
|
+
assert_equal true, form.errors.of_kind?(:email, :blank)
|
14
|
+
end
|
15
|
+
|
16
|
+
test "save!: raises a validation error if the current_organization is missing" do
|
17
|
+
form = form_class.new(current_organization: nil)
|
18
|
+
assert_raises ActiveModel::ValidationError do
|
19
|
+
form.save!
|
20
|
+
end
|
21
|
+
|
22
|
+
assert_equal true, form.errors.of_kind?(:current_organization, :blank)
|
23
|
+
end
|
24
|
+
|
25
|
+
test "save!: raises validation errors from the underlying MembershipInvitation object" do
|
26
|
+
form = form_class.new(email: Faker::Internet.email,
|
27
|
+
current_organization: organizations.organization_1,
|
28
|
+
sender: users.organization_2_owner,
|
29
|
+
membership_type: :organization_manager
|
30
|
+
)
|
31
|
+
|
32
|
+
assert_raises ActiveRecord::RecordInvalid do
|
33
|
+
form.save!
|
34
|
+
end
|
35
|
+
|
36
|
+
assert_equal true, form.errors.of_kind?(:sender, :cannot_manage_organization)
|
37
|
+
end
|
38
|
+
|
39
|
+
test "save!: creates a new MembershipInvitation and enqueues the email" do
|
40
|
+
email = Faker::Internet.email
|
41
|
+
form = form_class.new(email: email,
|
42
|
+
current_organization: organizations.organization_1,
|
43
|
+
sender: users.organization_1_manager,
|
44
|
+
membership_type: :staff
|
45
|
+
)
|
46
|
+
|
47
|
+
time = Time.now.utc
|
48
|
+
|
49
|
+
assert_difference "#{membership_invitation_class}.count", +1 do
|
50
|
+
assert_no_difference "#{membership_class}.count" do
|
51
|
+
Timecop.freeze(time) do
|
52
|
+
form.save!
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
assert_equal email, form.invitation.email
|
58
|
+
assert_equal "staff", form.invitation.membership_type
|
59
|
+
assert_equal organizations.organization_1, form.invitation.organization
|
60
|
+
assert_equal users.organization_1_manager, form.invitation.sender
|
61
|
+
|
62
|
+
assert_enqueued_email_with(MembershipInvitationMailer, :invitation, args: [{membership_invitation: form.invitation}])
|
63
|
+
assert_equal time.to_fs(:db), form.invitation.last_sent_at.to_fs(:db)
|
64
|
+
end
|
65
|
+
|
66
|
+
test "save!: re-sends a MembershipInvitation where we can re-send the email" do
|
67
|
+
email = Faker::Internet.email
|
68
|
+
|
69
|
+
existing_invitation = organizations.organization_1.membership_invitations.create!(email: email, sender: users.organization_1_owner, membership_type: :organization_manager)
|
70
|
+
|
71
|
+
form = form_class.new(email: email,
|
72
|
+
current_organization: organizations.organization_1,
|
73
|
+
sender: users.organization_1_manager,
|
74
|
+
membership_type: :staff
|
75
|
+
)
|
76
|
+
|
77
|
+
time = Time.now.utc
|
78
|
+
|
79
|
+
assert_no_difference "#{membership_invitation_class}.count" do
|
80
|
+
assert_no_difference "#{membership_class}.count" do
|
81
|
+
Timecop.freeze(time) do
|
82
|
+
form.save!
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
assert_equal email, form.invitation.email
|
88
|
+
assert_equal "organization_manager", form.invitation.membership_type
|
89
|
+
assert_equal organizations.organization_1, form.invitation.organization
|
90
|
+
assert_equal users.organization_1_owner, form.invitation.sender
|
91
|
+
|
92
|
+
assert_enqueued_email_with(MembershipInvitationMailer, :invitation, args: [{membership_invitation: existing_invitation}])
|
93
|
+
assert_equal time.to_fs(:db), form.invitation.last_sent_at.to_fs(:db)
|
94
|
+
end
|
95
|
+
|
96
|
+
test "save!: returns an error if the MembershipInvitation cannot be re-sent" do
|
97
|
+
user = users.organization_1_staff
|
98
|
+
email = user.email
|
99
|
+
existing_invitation = organizations.organization_1.membership_invitations.create!(email: email, sender: users.organization_1_manager, membership_type: :staff, user: user)
|
100
|
+
|
101
|
+
form = form_class.new(email: email,
|
102
|
+
current_organization: organizations.organization_1,
|
103
|
+
sender: users.organization_1_manager,
|
104
|
+
membership_type: :staff
|
105
|
+
)
|
106
|
+
|
107
|
+
assert_no_difference "#{membership_invitation_class}.count" do
|
108
|
+
assert_no_difference "#{membership_class}.count" do
|
109
|
+
assert_raises ActiveModel::ValidationError do
|
110
|
+
form.save!
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
assert_equal true, form.errors.of_kind?(:base, :cannot_be_resent)
|
116
|
+
assert_no_enqueued_emails
|
117
|
+
end
|
118
|
+
|
119
|
+
test "save!: returns an error if the Membership is archived_by_user" do
|
120
|
+
user = users.organization_1_staff
|
121
|
+
email = user.email
|
122
|
+
organization = organizations.organization_2
|
123
|
+
|
124
|
+
existing_membership = user.memberships.find_by!(organization: organization)
|
125
|
+
existing_membership.update!(state: :archived_by_user)
|
126
|
+
|
127
|
+
form = form_class.new(email: email,
|
128
|
+
current_organization: organizations.organization_2,
|
129
|
+
sender: users.organization_2_owner,
|
130
|
+
membership_type: :staff
|
131
|
+
)
|
132
|
+
|
133
|
+
assert_no_difference "#{membership_invitation_class}.count" do
|
134
|
+
assert_no_difference "#{membership_class}.count" do
|
135
|
+
assert_raises ActiveModel::ValidationError do
|
136
|
+
form.save!
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
assert_equal true, form.errors.of_kind?(:base, :cannot_be_resent)
|
142
|
+
assert_no_enqueued_emails
|
143
|
+
end
|
144
|
+
|
145
|
+
test "save!: transitions an archived_by_organization Membership to pending_reacceptance and stores the underlying membership" do
|
146
|
+
user = users.retired_staff
|
147
|
+
email = user.email
|
148
|
+
organization = organizations.organization_1
|
149
|
+
|
150
|
+
existing_membership = user.memberships.find_by!(organization: organization)
|
151
|
+
|
152
|
+
form = form_class.new(email: email,
|
153
|
+
current_organization: organizations.organization_1,
|
154
|
+
sender: users.organization_1_manager,
|
155
|
+
membership_type: :staff
|
156
|
+
)
|
157
|
+
|
158
|
+
assert_no_difference "#{membership_invitation_class}.count" do
|
159
|
+
assert_no_difference "#{membership_class}.count" do
|
160
|
+
form.save!
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
assert_equal existing_membership, form.existing_membership
|
165
|
+
assert_nil form.invitation
|
166
|
+
assert_equal "pending_reacceptance", form.existing_membership.state
|
167
|
+
assert_no_enqueued_emails
|
168
|
+
end
|
169
|
+
|
170
|
+
test "save!: does nothing to an already active Membership and stores the underlying membership" do
|
171
|
+
user = users.works_at_org_1_and_2
|
172
|
+
email = user.email
|
173
|
+
organization = organizations.organization_1
|
174
|
+
|
175
|
+
existing_membership = user.memberships.find_by!(organization: organization)
|
176
|
+
|
177
|
+
form = form_class.new(email: email,
|
178
|
+
current_organization: organizations.organization_1,
|
179
|
+
sender: users.organization_1_manager,
|
180
|
+
membership_type: :staff
|
181
|
+
)
|
182
|
+
|
183
|
+
assert_no_difference "#{membership_invitation_class}.count" do
|
184
|
+
assert_no_difference "#{membership_class}.count" do
|
185
|
+
form.save!
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
assert_equal existing_membership, form.existing_membership
|
190
|
+
assert_nil form.invitation
|
191
|
+
assert_equal "active", form.existing_membership.state
|
192
|
+
assert_no_enqueued_emails
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Forms::User::Membership
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "raises a validation error if trying to leave as the only organization_manager" do
|
8
|
+
user = users.organization_2_owner
|
9
|
+
organization = organizations.organization_2
|
10
|
+
membership = user.memberships.find_by!(organization: organization)
|
11
|
+
|
12
|
+
form = form_class.new(current_user: user,
|
13
|
+
membership: membership,
|
14
|
+
state: :archived_by_user
|
15
|
+
)
|
16
|
+
|
17
|
+
assert_raises ActiveModel::ValidationError do
|
18
|
+
form.save!
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_equal true, form.errors.of_kind?(:state, :cannot_be_archived)
|
22
|
+
assert_equal true, membership.reload.active?
|
23
|
+
end
|
24
|
+
|
25
|
+
test "can archive an organization_manager" do
|
26
|
+
user = users.organization_1_manager
|
27
|
+
organization = organizations.organization_1
|
28
|
+
membership = user.memberships.find_by!(organization: organization)
|
29
|
+
|
30
|
+
form = form_class.new(current_user: user,
|
31
|
+
membership: membership,
|
32
|
+
state: :archived_by_user
|
33
|
+
)
|
34
|
+
|
35
|
+
form.save!
|
36
|
+
|
37
|
+
assert_equal true, membership.reload.archived_by_user?
|
38
|
+
end
|
39
|
+
|
40
|
+
test "cannot change an archived_by_user membership" do
|
41
|
+
user = users.retired_staff
|
42
|
+
organization = organizations.organization_1
|
43
|
+
membership = user.memberships.find_by!(organization: organization)
|
44
|
+
membership.update!(state: :archived_by_user)
|
45
|
+
|
46
|
+
form = form_class.new(current_user: user,
|
47
|
+
membership: membership,
|
48
|
+
state: :pending_reacceptance
|
49
|
+
)
|
50
|
+
|
51
|
+
assert_raises ActiveModel::ValidationError do
|
52
|
+
form.save!
|
53
|
+
end
|
54
|
+
|
55
|
+
assert_equal true, form.errors.of_kind?(:state, :inclusion)
|
56
|
+
assert_equal true, membership.reload.archived_by_user?
|
57
|
+
end
|
58
|
+
|
59
|
+
test "can change a pending_reacceptance membership to archived_by_user" do
|
60
|
+
user = users.organization_1_staff
|
61
|
+
organization = organizations.organization_2
|
62
|
+
membership = user.memberships.find_by!(organization: organization)
|
63
|
+
|
64
|
+
form = form_class.new(current_user: user,
|
65
|
+
membership: membership,
|
66
|
+
state: :archived_by_user
|
67
|
+
)
|
68
|
+
|
69
|
+
form.save!
|
70
|
+
|
71
|
+
assert_equal true, membership.reload.archived_by_user?
|
72
|
+
end
|
73
|
+
|
74
|
+
test "cannot change the membership_type" do
|
75
|
+
user = users.organization_2_owner
|
76
|
+
organization = organizations.organization_2
|
77
|
+
membership = user.memberships.find_by!(organization: organization)
|
78
|
+
|
79
|
+
assert_raises ActiveModel::UnknownAttributeError do
|
80
|
+
form = form_class.new(current_user: user,
|
81
|
+
membership: membership,
|
82
|
+
membership_type: :staff
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|