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,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Models::Membership::Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "state: has at least the base set of cases" do
|
8
|
+
expected_states = [
|
9
|
+
:pending_reacceptance,
|
10
|
+
:active,
|
11
|
+
:archived_by_organization,
|
12
|
+
:archived_by_user,
|
13
|
+
].map(&:to_sym).to_set
|
14
|
+
|
15
|
+
actual = model_class.states.keys.map(&:to_sym).to_set
|
16
|
+
|
17
|
+
assert_equal true, expected_states.subset?(actual), [expected_states, actual]
|
18
|
+
end
|
19
|
+
|
20
|
+
test "belongs_to the user resource" do
|
21
|
+
reflection = model_class.reflect_on_association(user_reflection_name)
|
22
|
+
assert_equal :belongs_to, reflection.macro
|
23
|
+
end
|
24
|
+
|
25
|
+
test "belongs_to the organization resource" do
|
26
|
+
reflection = model_class.reflect_on_association(organization_reflection_name)
|
27
|
+
assert_equal :belongs_to, reflection.macro
|
28
|
+
end
|
29
|
+
|
30
|
+
test "user: must be unique for an organization" do
|
31
|
+
instance = model_instance
|
32
|
+
|
33
|
+
assert_equal true, instance.valid?
|
34
|
+
|
35
|
+
new_instance = instance.send(:"#{user_reflection_name}").memberships.build(organization: organization_instance)
|
36
|
+
|
37
|
+
assert_equal false, new_instance.valid?
|
38
|
+
assert_equal true, new_instance.errors.of_kind?(:user, :taken)
|
39
|
+
|
40
|
+
new_instance = other_user_instance.memberships.build(organization: instance.organization)
|
41
|
+
new_instance.valid?
|
42
|
+
assert_equal false, new_instance.errors.of_kind?(:user, :taken)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Models::MembershipInvitation::Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "belongs_to an organization" do
|
8
|
+
reflection = model_class.reflect_on_association(organization_reflection_name)
|
9
|
+
assert_equal :belongs_to, reflection.macro
|
10
|
+
end
|
11
|
+
|
12
|
+
test "optionally belongs_to a user" do
|
13
|
+
reflection = model_class.reflect_on_association(user_reflection_name)
|
14
|
+
assert_equal :belongs_to, reflection.macro
|
15
|
+
assert_equal user_class.to_s, reflection.class_name
|
16
|
+
assert_equal true, reflection.options[:optional]
|
17
|
+
end
|
18
|
+
|
19
|
+
test "optionally belongs_to a membership" do
|
20
|
+
reflection = model_class.reflect_on_association(membership_reflection_name)
|
21
|
+
assert_equal :belongs_to, reflection.macro
|
22
|
+
assert_equal membership_class.to_s, reflection.class_name
|
23
|
+
assert_equal true, reflection.options[:optional]
|
24
|
+
end
|
25
|
+
|
26
|
+
test "does not require a membership or user" do
|
27
|
+
instance = valid_new_instance
|
28
|
+
assert_equal true, instance.valid?
|
29
|
+
assert_nil valid_new_instance.user
|
30
|
+
assert_nil valid_new_instance.membership
|
31
|
+
end
|
32
|
+
|
33
|
+
test "requires an organization" do
|
34
|
+
instance = valid_new_instance
|
35
|
+
instance.organization = nil
|
36
|
+
assert_equal false, instance.valid?
|
37
|
+
assert_equal true, instance.errors.of_kind?(organization_reflection_name, :blank)
|
38
|
+
end
|
39
|
+
|
40
|
+
test "requires a non-blank email" do
|
41
|
+
instance = valid_new_instance
|
42
|
+
instance.email = nil
|
43
|
+
assert_equal false, instance.valid?
|
44
|
+
assert_equal true, instance.errors.of_kind?(:email, :blank)
|
45
|
+
|
46
|
+
instance.email = ""
|
47
|
+
assert_equal false, instance.valid?
|
48
|
+
assert_equal true, instance.errors.of_kind?(:email, :blank)
|
49
|
+
|
50
|
+
instance.email = " "
|
51
|
+
assert_equal false, instance.valid?
|
52
|
+
assert_equal true, instance.errors.of_kind?(:email, :blank)
|
53
|
+
end
|
54
|
+
|
55
|
+
test "an organization can only create 1 invitation per email" do
|
56
|
+
organization = organization_instance
|
57
|
+
email = Faker::Internet.email
|
58
|
+
instance_1 = create_new_instance(organization: organization, email: email)
|
59
|
+
|
60
|
+
duplicate_instance = valid_new_instance
|
61
|
+
duplicate_instance.email = email
|
62
|
+
duplicate_instance.organization = organization
|
63
|
+
|
64
|
+
assert_equal false, duplicate_instance.valid?
|
65
|
+
assert_equal true, duplicate_instance.errors.of_kind?(:email, :taken)
|
66
|
+
end
|
67
|
+
|
68
|
+
test "unused scope returns all unused invitations" do
|
69
|
+
assert_equal true, model_class.all.any?{|x| x.user.present?}
|
70
|
+
|
71
|
+
invitations = model_class.unused
|
72
|
+
assert_not_empty invitations
|
73
|
+
assert_equal true, invitations.all?{|x| x.user.nil? }
|
74
|
+
end
|
75
|
+
|
76
|
+
test "visible scope returns all visible invitations" do
|
77
|
+
hidden_invitation = hidden_instance
|
78
|
+
|
79
|
+
assert_equal false, hidden_invitation.visible?
|
80
|
+
|
81
|
+
assert_not_empty model_class.visible
|
82
|
+
assert_not_includes model_class.visible, hidden_invitation
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Models::MembershipInvitation::Sending
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "belongs_to an sender" do
|
8
|
+
reflection = model_class.reflect_on_association(sender_reflection_name)
|
9
|
+
assert_equal :belongs_to, reflection.macro
|
10
|
+
end
|
11
|
+
|
12
|
+
test "generates_token_for invitation that expires when the user is set" do
|
13
|
+
instance = valid_new_instance
|
14
|
+
instance.save!
|
15
|
+
|
16
|
+
token = instance.generate_token_for(:invitation)
|
17
|
+
|
18
|
+
assert_equal instance, model_class.find_by_token_for(:invitation, token)
|
19
|
+
|
20
|
+
instance.update!(user: user_instance)
|
21
|
+
|
22
|
+
assert_nil model_class.find_by_token_for(:invitation, token)
|
23
|
+
end
|
24
|
+
|
25
|
+
test "only validates that the sender has a membership in the organization on creation" do
|
26
|
+
invitation = valid_new_instance
|
27
|
+
other_user = other_user_instance_with_admin_privledges
|
28
|
+
|
29
|
+
assert_not_includes other_user.organizations, invitation.organization
|
30
|
+
|
31
|
+
invitation.sender = other_user
|
32
|
+
|
33
|
+
assert_equal false, invitation.valid?
|
34
|
+
assert_equal true, invitation.errors.of_kind?(:sender, :cannot_manage_organization)
|
35
|
+
|
36
|
+
invitation.sender = other_user_instance_without_admin_privledges
|
37
|
+
|
38
|
+
assert_equal false, invitation.valid?
|
39
|
+
assert_equal true, invitation.errors.of_kind?(:sender, :cannot_manage_organization)
|
40
|
+
|
41
|
+
invitation.sender = user_in_organization
|
42
|
+
|
43
|
+
assert_equal true, invitation.valid?
|
44
|
+
|
45
|
+
invitation.save!
|
46
|
+
|
47
|
+
assert_equal true, invitation.valid?
|
48
|
+
|
49
|
+
invitation.sender = other_user_instance_without_admin_privledges
|
50
|
+
assert_equal true, invitation.valid?
|
51
|
+
end
|
52
|
+
|
53
|
+
test "can_be_resent?: has a 10 minute waiting period" do
|
54
|
+
membership_invitation = model_class.first
|
55
|
+
|
56
|
+
assert_nil membership_invitation.last_sent_at
|
57
|
+
assert_equal true, membership_invitation.can_be_resent?
|
58
|
+
|
59
|
+
time = Time.now.utc
|
60
|
+
|
61
|
+
membership_invitation.update!(last_sent_at: time)
|
62
|
+
|
63
|
+
Timecop.freeze(time + 2.minutes) do
|
64
|
+
assert_equal false, membership_invitation.can_be_resent?
|
65
|
+
end
|
66
|
+
|
67
|
+
Timecop.freeze(time + 5.minutes) do
|
68
|
+
assert_equal false, membership_invitation.can_be_resent?
|
69
|
+
end
|
70
|
+
|
71
|
+
Timecop.freeze(time + 10.minutes) do
|
72
|
+
assert_equal true, membership_invitation.can_be_resent?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/app/lib/practical/test/shared/memberships/models/membership_invitation/use_for_and_notify.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Models::MembershipInvitation::UseForAndNotify
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "use_for_and_notify!: raises AlreadyUsedError if a user is present" do
|
8
|
+
invitation = instance_with_user
|
9
|
+
|
10
|
+
assert_raises model_class::AlreadyUsedError do
|
11
|
+
invitation.use_for_and_notify!(user: other_user)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
test "use_for_and_notify!: raises an ActiveRecord::RecordInvalid error if a membership already exists somehow for this user + organization" do
|
16
|
+
user = invited_user_instance
|
17
|
+
invitation = model_class.find_by!(email: user.email)
|
18
|
+
|
19
|
+
invitation.organization.memberships.create!(
|
20
|
+
user: user, membership_type: :staff, state: :pending_reacceptance
|
21
|
+
)
|
22
|
+
|
23
|
+
assert_raises ActiveRecord::RecordInvalid do
|
24
|
+
invitation.use_for_and_notify!(user: user)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
test """use_for_and_notify!:
|
29
|
+
- links to the given user
|
30
|
+
- creates the Membership with the stored membership_type
|
31
|
+
- matching mailer class: passkey_added
|
32
|
+
""" do
|
33
|
+
user = invited_user_instance
|
34
|
+
|
35
|
+
invitation = model_class.find_by(email: user.email)
|
36
|
+
|
37
|
+
assert_difference "Membership.count", +1 do
|
38
|
+
invitation.use_for_and_notify!(user: user)
|
39
|
+
invitation.reload
|
40
|
+
|
41
|
+
assert_equal user, invitation.user
|
42
|
+
assert_equal true, invitation.membership.active?
|
43
|
+
assert_equal user, invitation.membership.user
|
44
|
+
assert_equal invitation.organization, invitation.membership.organization
|
45
|
+
additional_membership_accept_assertions(invitation: invitation)
|
46
|
+
end
|
47
|
+
|
48
|
+
assert_enqueued_email_with(
|
49
|
+
mailer_class,
|
50
|
+
:invitation_accepted,
|
51
|
+
args: [{ membership_invitation: invitation }]
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Models::Organization::Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "has_many membership resource, dependent: :destroy" do
|
8
|
+
reflection = model_class.reflect_on_association(membership_reflection_name)
|
9
|
+
assert_equal :has_many, reflection.macro
|
10
|
+
assert_equal :destroy, reflection.options[:dependent]
|
11
|
+
end
|
12
|
+
|
13
|
+
test "has_many membership_invitations resource, dependent: :destroy" do
|
14
|
+
reflection = model_class.reflect_on_association(membership_invitation_reflection_name)
|
15
|
+
assert_equal :has_many, reflection.macro
|
16
|
+
assert_equal :destroy, reflection.options[:dependent]
|
17
|
+
end
|
18
|
+
|
19
|
+
test "has_many users resource, through: :memberships resource" do
|
20
|
+
reflection = model_class.reflect_on_association(user_reflection_name)
|
21
|
+
assert_equal :has_many, reflection.macro
|
22
|
+
assert_equal membership_reflection_name, reflection.options[:through]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Models::User::Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "has_many membership resource" do
|
8
|
+
reflection = model_class.reflect_on_association(membership_reflection_name)
|
9
|
+
assert_equal :has_many, reflection.macro
|
10
|
+
end
|
11
|
+
|
12
|
+
test "has_many membership_invitations resource" do
|
13
|
+
reflection = model_class.reflect_on_association(membership_invitation_reflection_name)
|
14
|
+
assert_equal :has_many, reflection.macro
|
15
|
+
end
|
16
|
+
|
17
|
+
test "has_many organizations resource, through: :memberships resource" do
|
18
|
+
reflection = model_class.reflect_on_association(organization_reflection_name)
|
19
|
+
assert_equal :has_many, reflection.macro
|
20
|
+
assert_equal membership_reflection_name, reflection.options[:through]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Policies::Organization::BaseResource
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
included do
|
6
|
+
class TestClass
|
7
|
+
attr_accessor :organization
|
8
|
+
|
9
|
+
def initialize(organization:)
|
10
|
+
self.organization = organization
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestClassPolicy < policy_class
|
15
|
+
def show?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
test "pre_check? the record is part of the same_organization?" do
|
21
|
+
organization = organizations.organization_1
|
22
|
+
record = TestClass.new(organization: organization)
|
23
|
+
other_record = TestClass.new(organization: organizations.organization_2)
|
24
|
+
|
25
|
+
assert_equal true, TestClassPolicy.new(record, user: users.organization_1_owner, organization: organization).apply(:show?)
|
26
|
+
assert_equal false, TestClassPolicy.new(other_record, user: users.organization_1_owner, organization: organization).apply(:show?)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Policies::Organization::Membership
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
test "manage?: only true for memberships where the user has an organization_manager membership for the same organization" do
|
8
|
+
membership = users.works_at_org_1_and_2.memberships.find_by(organization: organizations.organization_1)
|
9
|
+
|
10
|
+
assert_equal true, policy_for(membership: membership, user: users.organization_1_owner).apply(:manage?)
|
11
|
+
assert_equal true, policy_for(membership: membership, user: users.organization_1_manager).apply(:manage?)
|
12
|
+
|
13
|
+
assert_equal false, policy_for(membership: membership, user: users.works_at_org_1_and_2).apply(:manage?)
|
14
|
+
assert_equal false, policy_for(membership: membership, user: users.archived_organization_1_manager).apply(:manage?)
|
15
|
+
assert_equal false, policy_for(membership: membership, user: users.organization_2_owner).apply(:manage?)
|
16
|
+
assert_equal false, policy_for(membership: membership, user: users.organization_1_staff).apply(:manage?)
|
17
|
+
assert_equal false, policy_for(membership: membership, user: users.retired_staff).apply(:manage?)
|
18
|
+
end
|
19
|
+
|
20
|
+
test "archive?: false if the user does not have an organization_manager membership in the same organization" do
|
21
|
+
membership = users.works_at_org_1_and_2.memberships.find_by(organization: organizations.organization_1)
|
22
|
+
|
23
|
+
assert_equal true, policy_for(membership: membership, user: users.organization_1_owner).apply(:archive?)
|
24
|
+
assert_equal true, policy_for(membership: membership, user: users.organization_1_manager).apply(:archive?)
|
25
|
+
|
26
|
+
assert_equal false, policy_for(membership: membership, user: users.works_at_org_1_and_2).apply(:archive?)
|
27
|
+
assert_equal false, policy_for(membership: membership, user: users.archived_organization_1_manager).apply(:archive?)
|
28
|
+
assert_equal false, policy_for(membership: membership, user: users.organization_2_owner).apply(:archive?)
|
29
|
+
assert_equal false, policy_for(membership: membership, user: users.organization_1_staff).apply(:archive?)
|
30
|
+
assert_equal false, policy_for(membership: membership, user: users.retired_staff).apply(:archive?)
|
31
|
+
end
|
32
|
+
|
33
|
+
test "archive?: true if the membership is not an organization_manager membership" do
|
34
|
+
membership = users.works_at_org_1_and_2.memberships.find_by(organization: organizations.organization_1)
|
35
|
+
|
36
|
+
assert_equal false, membership.organization_manager?
|
37
|
+
|
38
|
+
assert_equal true, policy_for(membership: membership, user: users.organization_1_owner).apply(:archive?)
|
39
|
+
assert_equal true, policy_for(membership: membership, user: users.organization_1_manager).apply(:archive?)
|
40
|
+
|
41
|
+
assert_equal false, policy_for(membership: membership, user: users.works_at_org_1_and_2).apply(:archive?)
|
42
|
+
assert_equal false, policy_for(membership: membership, user: users.archived_organization_1_manager).apply(:archive?)
|
43
|
+
assert_equal false, policy_for(membership: membership, user: users.organization_2_owner).apply(:archive?)
|
44
|
+
assert_equal false, policy_for(membership: membership, user: users.organization_1_staff).apply(:archive?)
|
45
|
+
assert_equal false, policy_for(membership: membership, user: users.retired_staff).apply(:archive?)
|
46
|
+
end
|
47
|
+
|
48
|
+
test "archive?: only true if the organization_manager membership can be removed" do
|
49
|
+
membership = users.organization_1_manager.memberships.find_by(organization: organizations.organization_1)
|
50
|
+
|
51
|
+
assert_equal true, membership.organization_manager?
|
52
|
+
assert_equal true, organization_policy(membership.organization, user: users.organization_1_owner).apply(:remove_organization_manager?)
|
53
|
+
|
54
|
+
assert_equal true, policy_for(membership: membership, user: users.organization_1_owner).apply(:archive?)
|
55
|
+
assert_equal true, policy_for(membership: membership, user: users.organization_1_manager).apply(:archive?)
|
56
|
+
|
57
|
+
assert_equal false, policy_for(membership: membership, user: users.works_at_org_1_and_2).apply(:archive?)
|
58
|
+
assert_equal false, policy_for(membership: membership, user: users.archived_organization_1_manager).apply(:archive?)
|
59
|
+
assert_equal false, policy_for(membership: membership, user: users.organization_2_owner).apply(:archive?)
|
60
|
+
assert_equal false, policy_for(membership: membership, user: users.organization_1_staff).apply(:archive?)
|
61
|
+
assert_equal false, policy_for(membership: membership, user: users.retired_staff).apply(:archive?)
|
62
|
+
|
63
|
+
other_membership = users.organization_2_owner.memberships.find_by(organization: organizations.organization_2)
|
64
|
+
assert_equal true, membership.organization_manager?
|
65
|
+
assert_equal false, organization_policy(other_membership.organization, user: users.organization_2_owner).apply(:remove_organization_manager?)
|
66
|
+
|
67
|
+
assert_equal false, policy_for(membership: membership, user: users.organization_2_owner).apply(:archive?)
|
68
|
+
end
|
69
|
+
|
70
|
+
test "relation only returns the memberships that are part of the same organization" do
|
71
|
+
assert_equal_set(
|
72
|
+
organizations.organization_1.memberships,
|
73
|
+
policy_for(
|
74
|
+
membership: users.works_at_org_1_and_2.memberships.find_by(organization: organizations.organization_1),
|
75
|
+
user: users.organization_1_manager
|
76
|
+
).apply_scope(all_memberships_relation, type: :active_record_relation)
|
77
|
+
)
|
78
|
+
|
79
|
+
assert_equal_set(
|
80
|
+
organizations.organization_2.memberships,
|
81
|
+
policy_for(
|
82
|
+
membership: users.works_at_org_1_and_2.memberships.find_by(organization: organizations.organization_2),
|
83
|
+
user: users.organization_2_owner
|
84
|
+
).apply_scope(all_memberships_relation, type: :active_record_relation)
|
85
|
+
)
|
86
|
+
|
87
|
+
assert_equal_set(
|
88
|
+
organizations.organization_1.memberships,
|
89
|
+
policy_for(
|
90
|
+
membership: users.works_at_org_1_and_2.memberships.find_by(organization: organizations.organization_1),
|
91
|
+
user: users.works_at_org_1_and_2
|
92
|
+
).apply_scope(all_memberships_relation, type: :active_record_relation)
|
93
|
+
)
|
94
|
+
|
95
|
+
assert_empty(
|
96
|
+
policy_for(
|
97
|
+
membership: users.works_at_org_1_and_2.memberships.find_by(organization: organizations.organization_1),
|
98
|
+
user: users.organization_2_owner
|
99
|
+
).apply_scope(all_memberships_relation, type: :active_record_relation)
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Policies::Organization::MembershipInvitation
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
def assert_can_manage(invitation:, user:)
|
8
|
+
assert_equal true, policy_for(invitation: invitation, user: user).apply(:manage?)
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_cannot_manage(invitation:, user:)
|
12
|
+
assert_equal false, policy_for(invitation: invitation, user: user).apply(:manage?)
|
13
|
+
end
|
14
|
+
|
15
|
+
test "manage?: only true for memberships where the user has an organization_manager membership for the same organization" do
|
16
|
+
invitation = organizations.organization_1.membership_invitations.create!(
|
17
|
+
email: Faker::Internet.email,
|
18
|
+
sender: users.organization_1_owner,
|
19
|
+
membership_type: :staff
|
20
|
+
)
|
21
|
+
|
22
|
+
assert_equal true, policy_for(invitation: invitation, user: users.organization_1_owner).apply(:manage?)
|
23
|
+
assert_equal true, policy_for(invitation: invitation, user: users.organization_1_manager).apply(:manage?)
|
24
|
+
|
25
|
+
assert_equal false, policy_for(invitation: invitation, user: users.works_at_org_1_and_2).apply(:manage?)
|
26
|
+
assert_equal false, policy_for(invitation: invitation, user: users.archived_organization_1_manager).apply(:manage?)
|
27
|
+
assert_equal false, policy_for(invitation: invitation, user: users.organization_2_owner).apply(:manage?)
|
28
|
+
assert_equal false, policy_for(invitation: invitation, user: users.organization_1_staff).apply(:manage?)
|
29
|
+
assert_equal false, policy_for(invitation: invitation, user: users.retired_staff).apply(:manage?)
|
30
|
+
end
|
31
|
+
|
32
|
+
test "archive?: false if the user does not have an organization_manager membership in the same organization" do
|
33
|
+
invitation = organizations.organization_1.membership_invitations.create!(
|
34
|
+
email: Faker::Internet.email,
|
35
|
+
sender: users.organization_1_owner,
|
36
|
+
membership_type: :staff
|
37
|
+
)
|
38
|
+
|
39
|
+
assert_equal true, policy_for(invitation: invitation, user: users.organization_1_owner).apply(:archive?)
|
40
|
+
assert_equal true, policy_for(invitation: invitation, user: users.organization_1_manager).apply(:archive?)
|
41
|
+
|
42
|
+
assert_equal false, policy_for(invitation: invitation, user: users.works_at_org_1_and_2).apply(:archive?)
|
43
|
+
assert_equal false, policy_for(invitation: invitation, user: users.archived_organization_1_manager).apply(:archive?)
|
44
|
+
assert_equal false, policy_for(invitation: invitation, user: users.organization_2_owner).apply(:archive?)
|
45
|
+
assert_equal false, policy_for(invitation: invitation, user: users.organization_1_staff).apply(:archive?)
|
46
|
+
assert_equal false, policy_for(invitation: invitation, user: users.retired_staff).apply(:archive?)
|
47
|
+
end
|
48
|
+
|
49
|
+
test "relation only returns the membership_invitations that are part of the same organization" do
|
50
|
+
organizations.organization_1.membership_invitations.create!(
|
51
|
+
email: Faker::Internet.email,
|
52
|
+
sender: users.organization_1_owner,
|
53
|
+
membership_type: :staff
|
54
|
+
)
|
55
|
+
|
56
|
+
organizations.organization_2.membership_invitations.create!(
|
57
|
+
email: Faker::Internet.email,
|
58
|
+
sender: users.organization_2_owner,
|
59
|
+
membership_type: :staff
|
60
|
+
)
|
61
|
+
|
62
|
+
assert_equal_set(
|
63
|
+
organizations.organization_1.membership_invitations,
|
64
|
+
relation_policy_for(
|
65
|
+
organization: organizations.organization_1,
|
66
|
+
user: users.organization_1_manager
|
67
|
+
).apply_scope(all_membership_invitations_relation, type: :active_record_relation)
|
68
|
+
)
|
69
|
+
|
70
|
+
assert_equal_set(
|
71
|
+
organizations.organization_2.membership_invitations,
|
72
|
+
relation_policy_for(
|
73
|
+
organization: organizations.organization_2,
|
74
|
+
user: users.organization_2_owner
|
75
|
+
).apply_scope(all_membership_invitations_relation, type: :active_record_relation)
|
76
|
+
)
|
77
|
+
|
78
|
+
assert_equal_set(
|
79
|
+
organizations.organization_1.membership_invitations,
|
80
|
+
relation_policy_for(
|
81
|
+
organization: organizations.organization_1,
|
82
|
+
user: users.works_at_org_1_and_2
|
83
|
+
).apply_scope(all_membership_invitations_relation, type: :active_record_relation)
|
84
|
+
)
|
85
|
+
|
86
|
+
assert_empty(
|
87
|
+
relation_policy_for(
|
88
|
+
organization: organizations.organization_1,
|
89
|
+
user: users.organization_2_owner
|
90
|
+
).apply_scope(all_membership_invitations_relation, type: :active_record_relation)
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Policies::Organization::Resource::Inherits
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
included do
|
6
|
+
test "is a subclass of the resource_policy_class" do
|
7
|
+
assert_includes policy_class.ancestors, resource_policy_class
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Shared::Memberships::Policies::Organization
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
included do
|
6
|
+
test "default scope: returns all the organizations the user has access to" do
|
7
|
+
assert_equal_set(
|
8
|
+
[organizations.organization_1],
|
9
|
+
relation_policy(user: users.organization_1_owner)
|
10
|
+
)
|
11
|
+
|
12
|
+
assert_equal_set(
|
13
|
+
[organizations.organization_1],
|
14
|
+
relation_policy(user: users.organization_1_manager)
|
15
|
+
)
|
16
|
+
|
17
|
+
assert_equal_set(
|
18
|
+
[organizations.organization_1, organizations.organization_2],
|
19
|
+
relation_policy(user: users.works_at_org_1_and_2)
|
20
|
+
)
|
21
|
+
|
22
|
+
assert_equal_set(
|
23
|
+
[organizations.organization_2],
|
24
|
+
relation_policy(user: users.organization_2_owner)
|
25
|
+
)
|
26
|
+
|
27
|
+
assert_empty relation_policy(user: users.organization_1_staff)
|
28
|
+
assert_empty relation_policy(user: users.retired_staff)
|
29
|
+
assert_empty relation_policy(user: users.archived_organization_1_manager)
|
30
|
+
end
|
31
|
+
|
32
|
+
test "show?: only true when the user has an active membership" do
|
33
|
+
organization = organizations.organization_1
|
34
|
+
|
35
|
+
assert_equal true, policy(organization: organization, user: users.organization_1_owner).apply(:show?)
|
36
|
+
assert_equal true, policy(organization: organization, user: users.organization_1_manager).apply(:show?)
|
37
|
+
assert_equal true, policy(organization: organization, user: users.works_at_org_1_and_2).apply(:show?)
|
38
|
+
|
39
|
+
assert_equal false, policy(organization: organization, user: users.retired_staff).apply(:show?)
|
40
|
+
assert_equal false, policy(organization: organization, user: users.archived_organization_1_manager).apply(:show?)
|
41
|
+
|
42
|
+
assert_equal false, policy(organization: organization, user: users.organization_1_staff).apply(:show?)
|
43
|
+
assert_equal false, policy(organization: organization, user: users.organization_2_owner).apply(:show?)
|
44
|
+
end
|
45
|
+
|
46
|
+
test "manage?: only true when the user has an active organization_manager membership" do
|
47
|
+
organization = organizations.organization_1
|
48
|
+
|
49
|
+
assert_equal true, policy(organization: organization, user: users.organization_1_owner).apply(:manage?)
|
50
|
+
assert_equal true, policy(organization: organization, user: users.organization_1_manager).apply(:manage?)
|
51
|
+
|
52
|
+
assert_equal false, policy(organization: organization, user: users.works_at_org_1_and_2).apply(:manage?)
|
53
|
+
assert_equal false, policy(organization: organization, user: users.retired_staff).apply(:manage?)
|
54
|
+
assert_equal false, policy(organization: organization, user: users.archived_organization_1_manager).apply(:manage?)
|
55
|
+
|
56
|
+
assert_equal false, policy(organization: organization, user: users.organization_1_staff).apply(:manage?)
|
57
|
+
assert_equal false, policy(organization: organization, user: users.organization_2_owner).apply(:manage?)
|
58
|
+
end
|
59
|
+
|
60
|
+
test "remove_organization_manager?: only allowed if more than 1 organization_manager" do
|
61
|
+
organization_1 = organizations.organization_1
|
62
|
+
assert_equal true, policy_class.new(organization_1, user: users.organization_1_owner).apply(:remove_organization_manager?)
|
63
|
+
assert_equal false, policy_class.new(organizations.organization_2, user: users.organization_2_owner).apply(:remove_organization_manager?)
|
64
|
+
|
65
|
+
organization_1.memberships.where(user: users.organization_1_manager).delete_all
|
66
|
+
|
67
|
+
assert_equal false, policy_class.new(organization_1, user: users.organization_1_owner).apply(:remove_organization_manager?)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|