0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 05:42:36 +08:00
discourse/spec/models/group_spec.rb
Martin Brennan 943c1ba4cf
FIX: Make auto pseudogroups visible to logged on users (#41498)
With the ACL changes that are ongoing, regular users
need to be able to see the pseudogroups in certain situations,
like the ACL group selection dropdown. Pseudogroups are the
everyone, logged_in_users, and anonymous_users groups. They
are currently limited to staff only, but this change will allow
all logged on users to see them as well.

This commit also does some minor fixes related to
granular_anonymous_and_logged_in_groups_permissions
upcoming change. If this is enabled, the everyone group should not
be selectable in Site.groups in the UI, that will just lead to
confusion in the long term especially because this upcoming change
will eventually remove the everyone group.
2026-07-08 09:28:18 +10:00

1973 lines
65 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe Group do
fab!(:admin)
fab!(:user)
fab!(:group)
it_behaves_like "it has custom fields"
describe "Validations" do
it { is_expected.to validate_length_of(:bio_raw).is_at_most(3000) }
it { is_expected.to validate_length_of(:membership_request_template).is_at_most(5000) }
it { is_expected.to validate_length_of(:full_name).is_at_most(100) }
describe "#grant_trust_level" do
describe "when trust level is not valid" do
it "should not be valid" do
group.grant_trust_level = 123_456
expect(group.valid?).to eq(false)
expect(group.errors.full_messages.join(",")).to eq(
I18n.t("groups.errors.grant_trust_level_not_valid", trust_level: 123_456),
)
end
end
end
describe "#name" do
context "when a user with a similar name exists" do
it "should not be valid" do
new_group = Fabricate.build(:group, name: admin.username.upcase)
expect(new_group).to_not be_valid
expect(new_group.errors.full_messages.first).to include(
I18n.t("activerecord.errors.messages.taken"),
)
end
end
context "when a group with a similar name exists" do
it "should not be valid" do
new_group = Fabricate.build(:group, name: group.name.upcase)
expect(new_group).to_not be_valid
expect(new_group.errors.full_messages.first).to include(
I18n.t("activerecord.errors.messages.taken"),
)
end
end
context "when a group with a reserved name is created" do
it "should not be valid" do
new_group = Fabricate.build(:group, name: "by-id")
expect(new_group).to_not be_valid
expect(new_group.errors.full_messages.first).to include(
I18n.t("activerecord.errors.messages.reserved", name: "by-id"),
)
end
end
end
end
describe ".human_users" do
before { group.users << user << Discourse.system_user }
it "returns only human users" do
expect(group.human_users).to contain_exactly(user)
end
end
describe "#posts_for" do
it "returns the post in the group" do
p = Fabricate(:post)
group.add(p.user)
posts = group.posts_for(Guardian.new)
expect(posts).to include(p)
end
it "doesn't include unlisted posts" do
p = Fabricate(:post)
p.topic.update_column(:visible, false)
group.add(p.user)
posts = group.posts_for(Guardian.new)
expect(posts).not_to include(p)
end
it "filters results by datetime using the before parameter" do
p1 = Fabricate(:post)
p2 = Fabricate(:post, created_at: p1.created_at + 2.minutes)
group.add(p1.user)
posts = group.posts_for(Guardian.new, before: p1.created_at + 1.minute)
expect(posts).to include(p1)
expect(posts).not_to include(p2)
end
end
describe "#set_message_default_notification_levels!" do
context "with too many users in a group" do
fab!(:topic)
fab!(:large_group) { Fabricate(:group, messageable_level: Group::ALIAS_LEVELS[:everyone]) }
before do
SiteSetting.group_pm_user_limit = 1
Fabricate.times(2, :user).each { |user| large_group.add(user) }
end
it "raises a GroupPmUserLimitExceededError error" do
expect do
large_group.reload.set_message_default_notification_levels!(topic)
end.to raise_error(
Group::GroupPmUserLimitExceededError,
I18n.t(
"groups.errors.default_notification_level_users_limit",
count: SiteSetting.group_pm_user_limit,
group_name: large_group.name,
),
)
end
end
end
describe "#builtin" do
context "when verifying enum sequence" do
it "'moderators' should be at 1st position" do
expect(described_class.builtin[:moderators]).to eq(1)
end
it "'trust_level_2' should be at 4th position" do
expect(described_class.builtin[:trust_level_2]).to eq(4)
end
end
end
# UGLY but perf is horrible with this callback
before { User.set_callback(:create, :after, :ensure_in_trust_level_group) }
after { User.skip_callback(:create, :after, :ensure_in_trust_level_group) }
describe "validation" do
let(:group) { build(:group) }
it "is invalid for blank" do
group.name = ""
expect(group.valid?).to eq false
end
it "is valid for a longer name" do
group.name = "this_is_a_name"
expect(group.valid?).to eq true
end
it "is invalid for non names" do
group.name = "this is_a_name"
expect(group.valid?).to eq false
end
it "strips trailing and leading spaces" do
group.name = " dragon "
expect(group.save).to eq(true)
expect(group.reload.name).to eq("dragon")
end
it "is invalid for case-insensitive existing names" do
build(:group, name: "this_is_a_name").save
group.name = "This_Is_A_Name"
expect(group.valid?).to eq false
end
it "is invalid for poorly formatted domains" do
group.automatic_membership_email_domains = "wikipedia.org|not a domain"
expect(group.valid?).to eq false
end
it "is valid for proper domains" do
group.automatic_membership_email_domains = "discourse.org|wikipedia.org"
expect(group.valid?).to eq true
end
it "normalizes domains that include an @ or surrounding whitespace" do
group.automatic_membership_email_domains = " @discourse.org | jane@wikipedia.org "
expect(group.valid?).to eq true
expect(group.automatic_membership_email_domains).to eq("discourse.org|wikipedia.org")
end
it "is invalid for too many domains" do
SiteSetting.max_automatic_membership_email_domains = 1
group.automatic_membership_email_domains = "discourse.org|wikipedia.org"
expect(group).not_to be_valid
end
it "is invalid for too abnormally long domains" do
group.automatic_membership_email_domains = "#{"d" * 253}.org"
expect(group).not_to be_valid
end
it "is valid for newer TLDs" do
group.automatic_membership_email_domains = "discourse.institute"
expect(group.valid?).to eq true
end
it "is invalid for bad incoming email" do
group.incoming_email = "foo.bar.org"
expect(group.valid?).to eq(false)
end
it "is valid for proper incoming email" do
group.incoming_email = "foo@bar.org"
expect(group.valid?).to eq(true)
end
context "when a group has no owners" do
describe "group has not been persisted" do
it "should not allow membership requests" do
group = Fabricate.build(:group, allow_membership_requests: true)
expect(group.valid?).to eq(false)
expect(group.errors.full_messages).to include(
I18n.t("groups.errors.cant_allow_membership_requests"),
)
group.group_users.build(user_id: user.id, owner: true)
expect(group.valid?).to eq(true)
end
end
it "should not allow membership requests" do
group.allow_membership_requests = true
expect(group.valid?).to eq(false)
expect(group.errors.full_messages).to include(
I18n.t("groups.errors.cant_allow_membership_requests"),
)
group.allow_membership_requests = false
group.save!
group.add_owner(user)
group.allow_membership_requests = true
expect(group.valid?).to eq(true)
end
end
end
describe ".get_valid_email_domains" do
it "normalizes URLs, emails, ports, case, and surrounding whitespace to bare domains" do
expect(Group.get_valid_email_domains("HTTPS://Discourse.ORG/path")).to eq(["discourse.org"])
expect(Group.get_valid_email_domains("@discourse.org")).to eq(["discourse.org"])
expect(Group.get_valid_email_domains("jane@discourse.org")).to eq(["discourse.org"])
expect(Group.get_valid_email_domains("a@b@discourse.org")).to eq(["discourse.org"])
expect(Group.get_valid_email_domains(" discourse.org ")).to eq(["discourse.org"])
expect(Group.get_valid_email_domains("discourse.org:8080")).to eq(["discourse.org"])
end
it "collapses entries that normalize to the same domain, ignoring case" do
expect(Group.get_valid_email_domains("sales@acme.io|support@acme.io")).to eq(["acme.io"])
expect(Group.get_valid_email_domains("Acme.io|acme.io")).to eq(["acme.io"])
end
it "skips blank entries left by stray pipes or a bare @" do
invalid = []
valid = Group.get_valid_email_domains("acme.io||jane@") { |d| invalid << d }
expect(valid).to eq(["acme.io"])
expect(invalid).to be_empty
end
it "yields the normalized value for genuinely invalid domains" do
invalid = []
valid = Group.get_valid_email_domains("@discourse.org|not a domain") { |d| invalid << d }
expect(valid).to eq(["discourse.org"])
expect(invalid).to eq(["not a domain"])
end
end
describe "#primary_group=" do
before { group.add(user) }
it "updates all members' #primary_group" do
expect { group.update(primary_group: true) }.to change { user.reload.primary_group }.from(
nil,
).to(group)
expect { group.update(primary_group: false) }.to change { user.reload.primary_group }.from(
group,
).to(nil)
end
it "updates all members' #flair_group" do
expect { group.update(primary_group: true) }.to change { user.reload.flair_group }.from(
nil,
).to(group)
expect { group.update(primary_group: false) }.to change { user.reload.flair_group }.from(
group,
).to(nil)
end
end
describe "#title=" do
it "updates the member's title only if it was blank or exact match" do
group.add(user)
expect { group.update(title: "Awesome") }.to change { user.reload.title }.from(nil).to(
"Awesome",
)
expect { group.update(title: "Super") }.to change { user.reload.title }.from("Awesome").to(
"Super",
)
user.update(title: "Differently Awesome")
expect { group.update(title: "Awesome") }.to_not change { user.reload.title }
end
it "doesn't update non-member's title" do
user.update(title: group.title)
expect { group.update(title: "Super") }.to_not change { user.reload.title }
end
end
describe ".auto_groups_between" do
it "returns the auto groups between lower and upper bounds" do
expect(
described_class.auto_groups_between(:trust_level_0, :trust_level_3),
).to contain_exactly(10, 11, 12, 13)
end
it "excludes the undefined and pseudo groups between staff and TL0" do
expect(described_class.auto_groups_between(:admins, :trust_level_0)).to contain_exactly(
1,
2,
3,
10,
)
end
it "returns an empty array when lower group is higher than upper group" do
expect(described_class.auto_groups_between(:trust_level_1, :trust_level_0)).to be_empty
end
it "returns an empty array when passing an unknown group" do
expect(described_class.auto_groups_between(:trust_level_0, :trust_level_1337)).to be_empty
end
it "excludes pseudogroups that encompass large sets of users" do
expect(described_class.auto_groups_between(:admins, :trust_level_1)).to contain_exactly(
1,
2,
3,
10,
11,
)
end
end
describe ".refresh_automatic_group!" do
it "does not include staged users in any automatic groups" do
staged = Fabricate(:staged, trust_level: 1)
Group.refresh_automatic_group!(:trust_level_0)
Group.refresh_automatic_group!(:trust_level_1)
expect(GroupUser.where(user_id: staged.id).count).to eq(0)
staged.unstage!
expect(GroupUser.where(user_id: staged.id).count).to eq(2)
end
describe "after updating automatic group members" do
fab!(:user)
it "triggers an event when a user is removed from an automatic group" do
tl3_users = Group.find(Group::AUTO_GROUPS[:trust_level_3])
tl3_users.add(user)
_events = DiscourseEvent.track_events { Group.refresh_automatic_group!(:trust_level_3) }
expect(GroupUser.exists?(group: tl3_users, user: user)).to eq(false)
publish_event_job_args = Jobs::PublishGroupMembershipUpdates.jobs.last["args"].first
expect(publish_event_job_args["user_ids"]).to include(user.id)
expect(publish_event_job_args["group_id"]).to eq(tl3_users.id)
expect(publish_event_job_args["type"]).to include("remove")
end
it "triggers an event when a user is added to an automatic group" do
tl0_users = Group.find(Group::AUTO_GROUPS[:trust_level_0])
expect(GroupUser.exists?(group: tl0_users, user: user)).to eq(false)
events = DiscourseEvent.track_events { Group.refresh_automatic_group!(:trust_level_0) }
expect(events).to include(event_name: :group_updated, params: [tl0_users])
expect(GroupUser.exists?(group: tl0_users, user: user)).to eq(true)
publish_event_job_args = Jobs::PublishGroupMembershipUpdates.jobs.last["args"].first
expect(publish_event_job_args["user_ids"]).to include(user.id)
expect(publish_event_job_args["group_id"]).to eq(tl0_users.id)
expect(publish_event_job_args["type"]).to eq("add")
end
it "clears flair_group_id when user is removed from an automatic group" do
moderators = Group.find(Group::AUTO_GROUPS[:moderators])
moderators.update!(flair_icon: "shield-halved")
user.update!(moderator: true, flair_group_id: moderators.id)
Group.refresh_automatic_group!(:moderators)
expect(GroupUser.exists?(group: moderators, user: user)).to eq(true)
expect(user.reload.flair_group_id).to eq(moderators.id)
user.update!(moderator: false)
Group.refresh_automatic_group!(:moderators)
expect(GroupUser.exists?(group: moderators, user: user)).to eq(false)
expect(user.reload.flair_group_id).to be_nil
end
it "clears primary_group_id when user is removed from an automatic group" do
moderators = Group.find(Group::AUTO_GROUPS[:moderators])
user.update!(moderator: true, primary_group_id: moderators.id)
Group.refresh_automatic_group!(:moderators)
expect(user.reload.primary_group_id).to eq(moderators.id)
user.update!(moderator: false)
Group.refresh_automatic_group!(:moderators)
expect(user.reload.primary_group_id).to be_nil
end
it "clears title when user is removed from an automatic group" do
moderators = Group.find(Group::AUTO_GROUPS[:moderators])
moderators.update!(title: "Moderator")
user.update!(moderator: true, title: "Moderator")
Group.refresh_automatic_group!(:moderators)
expect(user.reload.title).to eq("Moderator")
user.update!(moderator: false)
Group.refresh_automatic_group!(:moderators)
expect(user.reload.title).to be_nil
end
end
it "makes sure the everyone group is visible to logged in users" do
g = Group.refresh_automatic_group!(:everyone)
expect(g.visibility_level).to eq(Group.visibility_levels[:logged_on_users])
end
it "makes sure pseudogroups are visible and have no members" do
anon = Group.refresh_automatic_group!(:anonymous_users)
expect(anon.id).to eq(Group::AUTO_GROUPS[:anonymous_users])
expect(anon.visibility_level).to eq(Group.visibility_levels[:logged_on_users])
expect(GroupUser.where(group_id: anon.id).count).to eq(0)
logged_in = Group.refresh_automatic_group!(:logged_in_users)
expect(logged_in.id).to eq(Group::AUTO_GROUPS[:logged_in_users])
expect(logged_in.visibility_level).to eq(Group.visibility_levels[:logged_on_users])
expect(GroupUser.where(group_id: logged_in.id).count).to eq(0)
end
it "makes sure automatic groups are visible to logged on users" do
g = Group.refresh_automatic_group!(:moderators)
expect(g.visibility_level).to eq(Group.visibility_levels[:logged_on_users])
tl0 = Group.refresh_automatic_group!(:trust_level_0)
expect(tl0.visibility_level).to eq(Group.visibility_levels[:logged_on_users])
end
it "ensures that the moderators group is messageable by all" do
group = Group.find(Group::AUTO_GROUPS[:moderators])
group.update!(messageable_level: Group::ALIAS_LEVELS[:nobody])
Group.refresh_automatic_group!(:moderators)
expect(group.reload.messageable_level).to eq(Group::ALIAS_LEVELS[:everyone])
end
it "does not reset the localized name" do
I18n.locale = SiteSetting.default_locale = "fi"
group = Group.find(Group::AUTO_GROUPS[:everyone])
group.update!(name: I18n.t("groups.default_names.everyone"))
Group.refresh_automatic_group!(:everyone)
expect(group.reload.name).to eq(I18n.t("groups.default_names.everyone"))
I18n.locale = SiteSetting.default_locale = "en"
Group.refresh_automatic_group!(:everyone)
expect(group.reload.name).to eq(I18n.t("groups.default_names.everyone"))
end
it "uses the localized name if name has not been taken" do
I18n.locale = SiteSetting.default_locale = "de"
group = Group.refresh_automatic_group!(:staff)
expect(group.name).to_not eq("staff")
expect(group.name).to eq(I18n.t("groups.default_names.staff"))
end
it "does not use the localized name if name has already been taken when switching to a the english locale" do
I18n.locale = SiteSetting.default_locale = "de"
Group.refresh_automatic_group!(:staff)
Group.refresh_automatic_group!(:moderators)
moderator_group = Group.find(Group::AUTO_GROUPS[:moderators])
staff_group = Group.find(Group::AUTO_GROUPS[:staff])
expect(moderator_group.name).to eq("Moderatoren")
expect(staff_group.name).to eq("Team")
I18n.locale = SiteSetting.default_locale = "en"
Fabricate(:group, name: I18n.t("groups.default_names.staff").upcase)
Group.refresh_automatic_group!(:staff)
expect(staff_group.reload.name).to eq("Team")
Fabricate(:user, username: I18n.t("groups.default_names.moderators").upcase)
Group.refresh_automatic_group!(:moderators)
expect(moderator_group.reload.name).to eq("Moderatoren")
end
it "does not use the localized name if name has already been taken when switching to a non-english locale" do
moderator_group = Group.find(Group::AUTO_GROUPS[:moderators])
staff_group = Group.find(Group::AUTO_GROUPS[:staff])
expect(moderator_group.name).to eq("moderators")
expect(staff_group.name).to eq("staff")
I18n.locale = SiteSetting.default_locale = "de"
Fabricate(:group, name: I18n.t("groups.default_names.staff").upcase)
Group.refresh_automatic_group!(:staff)
expect(staff_group.reload.name).to eq(staff_group.name)
Fabricate(:user, username: I18n.t("groups.default_names.moderators").upcase)
Group.refresh_automatic_group!(:moderators)
expect(moderator_group.reload.name).to eq(moderator_group.name)
end
it "always uses the default locale" do
SiteSetting.default_locale = "de"
I18n.locale = "en"
group = Group.refresh_automatic_group!(:staff)
expect(group.name).to_not eq("staff")
expect(group.name).to eq(I18n.t("groups.default_names.staff", locale: "de"))
end
it "can save groups" do
# Update all short usernames to ensure that the future minimum username
# length is met for all existing usernames
User.find_each { |u| u.update!(username: u.username * 2) }
# This a corner case when a group has a short name that is technically no
# longer allowed by `min_username_length`
Group.find(Group::AUTO_GROUPS[:everyone]).update!(name: "all")
SiteSetting.min_username_length = 10
expect { Group.refresh_automatic_groups! }.not_to raise_error
end
end
it "Correctly handles removal of primary group" do
group = Fabricate(:group, flair_icon: "icon")
user = Fabricate(:user)
group.add(user)
group.save
user.primary_group = group
user.save
group.reload
group.remove(user)
group.save
user.reload
expect(user.primary_group).to eq nil
expect(user.flair_group_id).to eq nil
end
it "Can update moderator/staff/admin groups correctly" do
other_admin = Fabricate(:admin)
moderator = Fabricate(:moderator)
Group.refresh_automatic_groups!(:admins, :staff, :moderators)
expect(Group[:admins].human_users).to contain_exactly(admin, other_admin)
expect(Group[:moderators].human_users).to contain_exactly(moderator)
expect(Group[:staff].human_users).to contain_exactly(moderator, admin, other_admin)
other_admin.admin = false
other_admin.save
Group.refresh_automatic_group!(:admins)
expect(Group[:admins].human_users).to contain_exactly(admin)
moderator.revoke_moderation!
admin.grant_admin!
expect(Group[:admins].human_users).to contain_exactly(admin)
expect(Group[:staff].human_users).to contain_exactly(admin)
admin.revoke_admin!
expect(Group[:admins].human_users).to be_empty
expect(Group[:staff].human_users).to be_empty
admin.grant_moderation!
expect(Group[:moderators].human_users).to contain_exactly(admin)
expect(Group[:staff].human_users).to contain_exactly(admin)
admin.revoke_moderation!
expect(Group[:admins].human_users).to be_empty
expect(Group[:staff].human_users).to be_empty
# we need some work to set min username to 6
User
.where("length(username) < 6")
.each do |u|
u.username = u.username + "ZZZZZZ"
u.save!
end
SiteSetting.min_username_length = 6
Group.refresh_automatic_groups!(:staff)
# should not explode here
end
it "Correctly updates automatic trust level groups" do
user = Fabricate(:user)
expect(Group[:trust_level_0].user_ids).to include user.id
user.change_trust_level!(TrustLevel[1])
expect(Group[:trust_level_1].user_ids).to include user.id
user.change_trust_level!(TrustLevel[2])
expect(Group[:trust_level_1].user_ids).to include user.id
expect(Group[:trust_level_2].user_ids).to include user.id
user2 = Fabricate(:coding_horror)
user2.change_trust_level!(TrustLevel[3])
expect(Group[:trust_level_2].user_ids).to include(user.id, user2.id)
end
it "Correctly updates all automatic groups upon request" do
other_admin = Fabricate(:admin)
other_user = Fabricate(:user)
other_user.change_trust_level!(TrustLevel[2])
DB.exec("UPDATE groups SET user_count = 0 WHERE id = #{Group::AUTO_GROUPS[:trust_level_2]}")
Group.delete_all
Group.refresh_automatic_groups!
expect(Group.count).to eq Group::AUTO_GROUPS.count
g = Group[:admins]
expect(g.human_users.count).to eq(g.user_count)
expect(g.human_users).to contain_exactly(admin, other_admin)
g = Group[:admins]
expect(g.human_users.count).to eq(g.user_count)
expect(g.human_users).to contain_exactly(admin, other_admin)
g = Group[:trust_level_1]
expect(g.human_users.count).to eq(g.user_count)
expect(g.human_users).to contain_exactly(admin, other_admin, user, other_user)
g = Group[:trust_level_2]
expect(g.human_users.count).to eq(g.user_count)
expect(g.human_users).to contain_exactly(admin, other_admin, other_user)
end
it "can set members via usernames helper" do
g = Fabricate(:group)
u1 = Fabricate(:user)
u2 = Fabricate(:user)
u3 = Fabricate(:user)
g.add(u1)
g.save!
usernames = "#{u2.username},#{u3.username}"
# no side effects please
g.usernames = usernames
g.reload
expect(g.users.count).to eq 1
g.usernames = usernames
g.save!
expect(g.usernames.split(",").sort).to eq usernames.split(",").sort
end
describe "new" do
subject(:group) { Fabricate.build(:group) }
it "triggers a extensibility event" do
event = DiscourseEvent.track_events { group.save! }.first
expect(event[:event_name]).to eq(:group_created)
expect(event[:params].first).to eq(group)
end
end
describe "destroy" do
fab!(:user)
fab!(:group) { Fabricate(:group, users: [user]) }
before { group.add(user) }
it "it deleted correctly" do
group.destroy!
expect(User.where(id: user.id).count).to eq 1
expect(GroupUser.where(group_id: group.id).count).to eq 0
end
it "triggers a extensibility event" do
event = DiscourseEvent.track_events { group.destroy! }.first
expect(event[:event_name]).to eq(:group_destroyed)
expect(event[:params].first).to eq(group)
end
it "strips the user's title and unsets the user's primary group when exact match" do
group.update(title: "Awesome")
user.update(primary_group: group)
group.destroy!
user.reload
expect(user.title).to eq(nil)
expect(user.primary_group).to eq(nil)
end
it "does not strip title or unset primary group when not exact match" do
primary_group = Fabricate(:group, primary_group: true, title: "Different")
primary_group.add(user)
group.update(title: "Awesome")
group.destroy!
user.reload
expect(user.title).to eq("Different")
expect(user.primary_group).to eq(primary_group)
end
it "doesn't fail when the user gets destroyed" do
group.update(title: "Awesome")
group.add(user)
user.reload
UserDestroyer.new(Discourse.system_user).destroy(user)
end
end
it "has custom fields" do
group = Fabricate(:group)
expect(group.custom_fields["a"]).to be_nil
group.custom_fields["hugh"] = "jackman"
group.custom_fields["jack"] = "black"
group.save
group = Group.find(group.id)
expect(group.custom_fields).to eq("hugh" => "jackman", "jack" => "black")
end
it "allows you to lookup a new group by name" do
group = Fabricate(:group)
expect(group.id).to eq Group[group.name].id
expect(group.id).to eq Group[group.name.to_sym].id
end
it "allows you to lookup a group by integer id" do
group = Fabricate(:group)
expect(Group.lookup_groups(group_ids: group.id)).to contain_exactly(group)
end
it "allows you to lookup groups by comma separated string" do
group1 = Fabricate(:group)
group2 = Fabricate(:group)
expect(Group.lookup_groups(group_ids: "#{group1.id},#{group2.id}")).to contain_exactly(
group1,
group2,
)
end
it "allows you to lookup groups by array" do
group1 = Fabricate(:group)
group2 = Fabricate(:group)
expect(Group.lookup_groups(group_ids: [group1.id, group2.id])).to contain_exactly(
group1,
group2,
)
end
it "can find desired groups correctly" do
expect(Group.desired_trust_level_groups(2)).to contain_exactly(10, 11, 12)
end
it "correctly handles trust level changes" do
user = Fabricate(:user, trust_level: 2)
Group.user_trust_level_change!(user.id, 2)
expect(user.groups.map(&:name)).to match_array %w[trust_level_0 trust_level_1 trust_level_2]
Group.user_trust_level_change!(user.id, 0)
user.reload
expect(user.groups.map(&:name)).to contain_exactly("trust_level_0")
end
it "generates an event when applying group from trust level change" do
called = nil
block = Proc.new { |user, group| called = { user_id: user.id, group_id: group.id } }
begin
DiscourseEvent.on(:user_added_to_group, &block)
user = Fabricate(:user, trust_level: 2)
Group.user_trust_level_change!(user.id, 2)
expect(called).to eq(user_id: user.id, group_id: Group.find_by(name: "trust_level_2").id)
ensure
DiscourseEvent.off(:user_added_to_group, &block)
end
end
describe "group management" do
fab!(:group)
it "by default has no managers" do
expect(group.group_users.where("group_users.owner")).to be_empty
end
it "multiple managers can be appointed" do
2.times do |i|
u = Fabricate(:user)
group.add_owner(u)
end
expect(group.group_users.where("group_users.owner").count).to eq(2)
end
it "manager has authority to edit membership" do
u = Fabricate(:user)
expect(Guardian.new(u).can_edit?(group)).to be_falsy
group.add_owner(u)
expect(Guardian.new(u).can_edit?(group)).to be_truthy
end
end
describe "trust level management" do
before { Jobs.run_immediately! }
it "correctly grants a trust level to members" do
group = Fabricate(:group, grant_trust_level: 2)
u0 = Fabricate(:user, trust_level: 0)
u3 = Fabricate(:user, trust_level: 3)
group.add(u0)
expect(u0.reload.trust_level).to eq(2)
group.add(u3)
expect(u3.reload.trust_level).to eq(3)
end
describe "when a user has qualified for trust level 1" do
fab!(:user) { Fabricate(:user, trust_level: 1, created_at: 10.years.ago) }
fab!(:group) { Fabricate(:group, grant_trust_level: 3) }
fab!(:group2) { Fabricate(:group, grant_trust_level: 2) }
before { user.user_stat.update!(topics_entered: 999, posts_read_count: 999, time_read: 999) }
it "should not demote the user" do
group.add(user)
group2.add(user)
expect(user.reload.trust_level).to eq(3)
group.remove(user)
expect(user.reload.trust_level).to eq(2)
group2.remove(user)
expect(user.reload.trust_level).to eq(1)
end
end
it "adjusts the user trust level" do
g0 = Fabricate(:group, grant_trust_level: 2)
g1 = Fabricate(:group, grant_trust_level: 3)
g2 = Fabricate(:group)
user = Fabricate(:user, trust_level: 0)
# Add a group without one to consider `NULL` check
g2.add(user)
expect(user.group_granted_trust_level).to be_nil
expect(user.manual_locked_trust_level).to be_nil
g0.add(user)
expect(user.reload.trust_level).to eq(2)
expect(user.group_granted_trust_level).to eq(2)
expect(user.manual_locked_trust_level).to be_nil
g1.add(user)
expect(user.reload.trust_level).to eq(3)
expect(user.group_granted_trust_level).to eq(3)
expect(user.manual_locked_trust_level).to be_nil
g1.remove(user)
expect(user.reload.trust_level).to eq(2)
expect(user.group_granted_trust_level).to eq(2)
expect(user.manual_locked_trust_level).to be_nil
g0.remove(user)
user.reload
expect(user.manual_locked_trust_level).to be_nil
expect(user.group_granted_trust_level).to be_nil
expect(user.trust_level).to eq(0)
end
end
it "should cook the bio" do
group = Fabricate(:group)
group.update!(bio_raw: "This is a group for :unicorn: lovers")
expect(group.bio_cooked).to include("unicorn.png")
group.update!(bio_raw: "")
expect(group.bio_cooked).to eq(nil)
end
describe ".visible_groups" do
def can_view?(user, group)
Group.visible_groups(user).where(id: group.id).exists?
end
it "includes everyone group when option is present" do
expect(
Group
.visible_groups(admin, [], include_everyone: true)
.where(id: Group::AUTO_GROUPS[:everyone])
.exists?,
).to eq(true)
end
it "doesn't include everyones group by default" do
expect(
Group
.visible_groups(admin, [], include_everyone: false)
.where(id: Group::AUTO_GROUPS[:everyone])
.exists?,
).to eq(false)
expect(
Group.visible_groups(admin, [], nil).where(id: Group::AUTO_GROUPS[:everyone]).exists?,
).to eq(false)
expect(
Group.visible_groups(admin, [], {}).where(id: Group::AUTO_GROUPS[:everyone]).exists?,
).to eq(false)
end
it "includes logged_in_users and anonymous_users groups when include_pseudogroups is true, and everyone when that is also requested" do
expect(
Group
.visible_groups(admin, [], include_pseudogroups: true)
.where(id: Group::AUTO_GROUPS[:anonymous_users])
.exists?,
).to eq(true)
expect(
Group
.visible_groups(admin, [], include_pseudogroups: true)
.where(id: Group::AUTO_GROUPS[:logged_in_users])
.exists?,
).to eq(true)
expect(
Group
.visible_groups(admin, [], include_pseudogroups: true)
.where(id: Group::AUTO_GROUPS[:everyone])
.exists?,
).to eq(false)
expect(
Group
.visible_groups(admin, [], include_pseudogroups: true, include_everyone: true)
.where(id: Group::AUTO_GROUPS[:everyone])
.exists?,
).to eq(true)
end
it "includes pseudogroups for regular users when requested" do
regular_user = Fabricate(:user)
Group.refresh_automatic_groups!(:everyone, :anonymous_users, :logged_in_users)
visible_group_ids =
Group.visible_groups(regular_user, [], include_pseudogroups: true).pluck(:id)
expect(visible_group_ids).to include(
Group::AUTO_GROUPS[:anonymous_users],
Group::AUTO_GROUPS[:logged_in_users],
)
expect(visible_group_ids).to_not include(Group::AUTO_GROUPS[:everyone])
expect(
Group
.visible_groups(nil, [], include_pseudogroups: true)
.where(
id: [
Group::AUTO_GROUPS[:everyone],
Group::AUTO_GROUPS[:anonymous_users],
Group::AUTO_GROUPS[:logged_in_users],
],
)
.exists?,
).to eq(false)
end
it "does not include logged_in_users, anonymous_users and everyone groups by default" do
expect(
Group.visible_groups(admin, []).where(id: Group::AUTO_GROUPS[:everyone]).exists?,
).to eq(false)
expect(
Group.visible_groups(admin, []).where(id: Group::AUTO_GROUPS[:anonymous_users]).exists?,
).to eq(false)
expect(
Group.visible_groups(admin, []).where(id: Group::AUTO_GROUPS[:logged_in_users]).exists?,
).to eq(false)
end
it "correctly restricts group visibility" do
group = Fabricate(:group, visibility_level: Group.visibility_levels[:owners])
logged_on_user = Fabricate(:user)
member = Fabricate(:user)
group.add(member)
owner = Fabricate(:user)
group.add_owner(owner)
moderator = Fabricate(:user, moderator: true)
admin = Fabricate(:user, admin: true)
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(false)
expect(can_view?(member, group)).to eq(false)
expect(can_view?(logged_on_user, group)).to eq(false)
expect(can_view?(nil, group)).to eq(false)
group.add_owner(moderator)
expect(can_view?(moderator, group)).to eq(true)
GroupUser.delete_by(group: group, user: moderator)
group.update_columns(visibility_level: Group.visibility_levels[:staff])
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(true)
expect(can_view?(member, group)).to eq(false)
expect(can_view?(logged_on_user, group)).to eq(false)
expect(can_view?(nil, group)).to eq(false)
group.update_columns(visibility_level: Group.visibility_levels[:members])
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(true)
expect(can_view?(member, group)).to eq(true)
expect(can_view?(logged_on_user, group)).to eq(false)
expect(can_view?(nil, group)).to eq(false)
group.update_columns(visibility_level: Group.visibility_levels[:public])
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(true)
expect(can_view?(member, group)).to eq(true)
expect(can_view?(logged_on_user, group)).to eq(true)
expect(can_view?(nil, group)).to eq(true)
group.update_columns(visibility_level: Group.visibility_levels[:logged_on_users])
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(true)
expect(can_view?(member, group)).to eq(true)
expect(can_view?(logged_on_user, group)).to eq(true)
expect(can_view?(nil, group)).to eq(false)
end
end
describe ".members_visible_groups" do
def can_view?(user, group)
Group.members_visible_groups(user).exists?(id: group.id)
end
it "correctly restricts group members visibility" do
group = Fabricate(:group, members_visibility_level: Group.visibility_levels[:owners])
logged_on_user = Fabricate(:user)
member = Fabricate(:user)
group.add(member)
owner = Fabricate(:user)
group.add_owner(owner)
moderator = Fabricate(:user, moderator: true)
admin = Fabricate(:user, admin: true)
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(false)
expect(can_view?(member, group)).to eq(false)
expect(can_view?(logged_on_user, group)).to eq(false)
expect(can_view?(nil, group)).to eq(false)
group.add_owner(moderator)
expect(can_view?(moderator, group)).to eq(true)
GroupUser.delete_by(group: group, user: moderator)
group.update_columns(members_visibility_level: Group.visibility_levels[:staff])
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(true)
expect(can_view?(member, group)).to eq(false)
expect(can_view?(logged_on_user, group)).to eq(false)
expect(can_view?(nil, group)).to eq(false)
group.update_columns(members_visibility_level: Group.visibility_levels[:members])
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(true)
expect(can_view?(member, group)).to eq(true)
expect(can_view?(logged_on_user, group)).to eq(false)
expect(can_view?(nil, group)).to eq(false)
group.update_columns(members_visibility_level: Group.visibility_levels[:public])
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(true)
expect(can_view?(member, group)).to eq(true)
expect(can_view?(logged_on_user, group)).to eq(true)
expect(can_view?(nil, group)).to eq(true)
group.update_columns(members_visibility_level: Group.visibility_levels[:logged_on_users])
expect(can_view?(admin, group)).to eq(true)
expect(can_view?(owner, group)).to eq(true)
expect(can_view?(moderator, group)).to eq(true)
expect(can_view?(member, group)).to eq(true)
expect(can_view?(logged_on_user, group)).to eq(true)
expect(can_view?(nil, group)).to eq(false)
end
end
describe "#remove" do
before { group.add(user) }
context "when stripping title" do
it "only strips user's title if exact match" do
group.update!(title: "Awesome")
expect { group.remove(user) }.to change { user.reload.title }.from("Awesome").to(nil)
group.add(user)
user.update_columns(title: "Different")
expect { group.remove(user) }.to_not change { user.reload.title }
end
it "grants another title when the user has other available titles" do
group.update!(title: "Awesome")
Fabricate(:group, title: "Super").add(user)
expect { group.remove(user) }.to change { user.reload.title }.from("Awesome").to("Super")
end
end
it "unsets the user's primary group" do
user.update(primary_group: group)
expect { group.remove(user) }.to change { user.reload.primary_group }.from(group).to(nil)
end
it "triggers a user_removed_from_group event" do
events = DiscourseEvent.track_events { group.remove(user) }.map { |e| e[:event_name] }
expect(events).to include(:user_removed_from_group)
end
describe "with webhook" do
fab!(:group_user_web_hook)
it "Enqueues webhook events" do
group.remove(user)
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
expect(job_args["event_name"]).to eq("user_removed_from_group")
payload = JSON.parse(job_args["payload"])
expect(payload["group_id"]).to eq(group.id)
expect(payload["user_id"]).to eq(user.id)
end
end
context "when publishing updates" do
fab!(:category)
before { group.update!(public_exit: true) }
it "should publish category removal when category is read-restricted to the group" do
category.set_permissions(group => :full)
category.save!
group.update!(categories: [category])
message = MessageBus.track_publish("/categories") { group.remove(user) }.first
expect(message.data[:deleted_categories]).to eq([category.id])
expect(message.data[:categories]).to be_blank
expect(message.user_ids).to eq([user.id])
end
it "should publish updated category permissions when category is readable by everyone" do
category.set_permissions(:everyone => :readonly, group => :full)
category.save!
group.update!(categories: [category])
message = MessageBus.track_publish("/categories") { group.remove(user) }.first
expect(message.data[:categories].count).to eq(1)
expect(message.data[:categories].first[:id]).to eq(category.id)
expect(message.data[:deleted_categories]).to be_blank
expect(message.user_ids).to eq([user.id])
end
describe "when group belongs to more than #{Group::PUBLISH_CATEGORIES_LIMIT} categories" do
it "should publish a message to refresh the user's client" do
group.categories += Fabricate.times(Group::PUBLISH_CATEGORIES_LIMIT + 1, :category)
message = MessageBus.track_publish { group.remove(user) }.first
expect(message.data).to eq("clobber")
expect(message.channel).to eq("/refresh_client")
expect(message.user_ids).to eq([user.id])
end
end
end
end
describe "#add" do
it "grants the title only if the new member does not have title" do
group.update(title: "Awesome")
expect { group.add(user) }.to change { user.reload.title }.from(nil).to("Awesome")
group.remove(user)
user.update(title: "Already Awesome")
expect { group.add(user) }.not_to change { user.reload.title }
end
it "always sets user's primary group" do
group.update(primary_group: true, title: "AAAA")
expect { group.add(user) }.to change { user.reload.primary_group }.from(nil).to(group)
new_group = Fabricate(:group, primary_group: true, title: "BBBB")
expect {
new_group.add(user)
user.reload
}.to change { user.primary_group }.from(group).to(new_group).and change { user.title }.from(
"AAAA",
).to("BBBB")
end
it "can send a notification to the user" do
expect { group.add(user, notify: true) }.to change { Notification.count }.by(1)
notification = Notification.last
expect(notification.notification_type).to eq(Notification.types[:membership_request_accepted])
expect(notification.user_id).to eq(user.id)
end
it "triggers a user_added_to_group event" do
automatic = nil
called = false
block =
Proc.new do |_u, _g, options|
automatic = options[:automatic]
called = true
end
begin
DiscourseEvent.on(:user_added_to_group, &block)
group.add(user)
expect(automatic).to eql(false)
expect(called).to eq(true)
ensure
DiscourseEvent.off(:user_added_to_group, &block)
end
end
context "when adding a user into a public group" do
fab!(:category)
it "should publish the group's categories to the client" do
group.update!(public_admission: true, categories: [category])
message = MessageBus.track_publish("/categories") { group.add(user) }.first
expect(message.data[:categories].count).to eq(1)
expect(message.data[:categories].first[:id]).to eq(category.id)
expect(message.user_ids).to eq([user.id])
end
describe "when group belongs to more than #{Group::PUBLISH_CATEGORIES_LIMIT} categories" do
it "should publish a message to refresh the user's client" do
group.categories += Fabricate.times(Group::PUBLISH_CATEGORIES_LIMIT + 1, :category)
message = MessageBus.track_publish { group.add(user) }.first
expect(message.data).to eq("clobber")
expect(message.channel).to eq("/refresh_client")
expect(message.user_ids).to eq([user.id])
end
end
end
end
describe ".search_groups" do
def search_group_names(name)
Group.search_groups(name, sort: :auto).map(&:name)
end
it "should return the right groups" do
Group.delete_all
group_name =
Fabricate(:group, name: "tEsT_more_things", full_name: "Abc something awesome").name
expect(search_group_names("te")).to eq([group_name])
expect(search_group_names("TE")).to eq([group_name])
expect(search_group_names("es")).to eq([group_name])
expect(search_group_names("ES")).to eq([group_name])
expect(search_group_names("ngs")).to eq([group_name])
expect(search_group_names("sOmEthi")).to eq([group_name])
expect(search_group_names("abc")).to eq([group_name])
expect(search_group_names("sOmEthi")).to eq([group_name])
expect(search_group_names("test2")).to eq([])
end
it "should prioritize prefix matches on group's name or fullname" do
Fabricate(:group, name: "pears_11", full_name: "fred apple")
Fabricate(:group, name: "apples", full_name: "jane orange")
Fabricate(:group, name: "oranges2", full_name: "nothing")
Fabricate(:group, name: "oranges1", full_name: "ms fred")
expect(search_group_names("ap")).to eq(%w[apples pears_11])
expect(search_group_names("fr")).to eq(%w[pears_11 oranges1])
expect(search_group_names("oran")).to eq(%w[oranges1 oranges2 apples])
expect(search_group_names("pearsX11")).to eq([])
end
end
describe "#bulk_add" do
it "adds multiple users and returns their IDs" do
result = group.bulk_add([user.id, admin.id])
expect(result).to contain_exactly(user.id, admin.id)
expect(group.group_users.map(&:user_id)).to contain_exactly(user.id, admin.id)
end
it "returns empty array for blank input" do
expect(group.bulk_add([])).to eq([])
expect(group.bulk_add(nil)).to eq([])
end
it "skips users already in the group" do
group.bulk_add([user.id])
result = group.bulk_add([user.id, admin.id])
expect(result).to eq([admin.id])
end
it "sets notification_level to group default" do
group.update!(default_notification_level: NotificationLevels.all[:watching])
group.bulk_add([user.id])
expect(GroupUser.find_by(group: group, user: user).notification_level).to eq(
NotificationLevels.all[:watching],
)
end
it "updates group user count" do
expect { group.bulk_add([user.id, admin.id]) }.to change { group.reload.user_count }.from(
0,
).to(2)
end
it "grants title to users without one" do
group.update!(title: "Awesome")
group.bulk_add([user.id])
expect(user.reload.title).to eq("Awesome")
end
it "does not overwrite an existing title" do
user.update!(title: "Already Great")
group.update!(title: "Awesome")
group.bulk_add([user.id])
expect(user.reload.title).to eq("Already Great")
end
it "sets primary_group_id and flair_group_id when group is primary" do
group.update!(primary_group: true)
group.bulk_add([user.id])
user.reload
expect(user.primary_group_id).to eq(group.id)
expect(user.flair_group_id).to eq(group.id)
end
it "replaces title from old primary group with new primary group title" do
old_group = Fabricate(:group, primary_group: true, title: "Old Title")
old_group.bulk_add([user.id])
expect(user.reload.title).to eq("Old Title")
group.update!(primary_group: true, title: "New Title")
group.bulk_add([user.id])
expect(user.reload.title).to eq("New Title")
expect(user.primary_group_id).to eq(group.id)
end
it "enqueues bulk_grant_trust_level job when group grants trust level" do
group.update!(grant_trust_level: 2)
expect { group.bulk_add([user.id, admin.id]) }.to change(
Jobs::BulkGrantTrustLevel.jobs,
:size,
).by(1)
job_args = Jobs::BulkGrantTrustLevel.jobs.last["args"].first
expect(job_args["trust_level"]).to eq(2)
expect(job_args["user_ids"]).to contain_exactly(user.id, admin.id)
end
it "does not enqueue trust level job when grant_trust_level is nil" do
expect_not_enqueued_with(job: :bulk_grant_trust_level) { group.bulk_add([user.id]) }
end
it "does not enqueue trust level job when grant_trust_level is zero" do
group.update!(grant_trust_level: 0)
expect_not_enqueued_with(job: :bulk_grant_trust_level) { group.bulk_add([user.id]) }
end
it "triggers user_added_to_group event for each user" do
user_ids = [user.id, admin.id]
events = DiscourseEvent.track_events(:user_added_to_group) { group.bulk_add(user_ids) }
expect(events.size).to eq(2)
expect(events.map { |e| e[:params][0] }).to contain_exactly(user, admin)
end
it "passes automatic flag through to the event" do
events =
DiscourseEvent.track_events(:user_added_to_group) do
group.bulk_add([user.id], automatic: true)
end
expect(events.first[:params][2][:automatic]).to eq(true)
end
it "sets category notification defaults" do
category = Fabricate(:category)
group.update!(watching_category_ids: [category.id])
group.bulk_add([user.id])
expect(CategoryUser.find_by(user: user, category: category).notification_level).to eq(
CategoryUser.notification_levels[:watching],
)
end
it "sets tag notification defaults" do
tag = Fabricate(:tag)
group.update!(watching_tags: [tag.name])
group.bulk_add([user.id])
expect(TagUser.find_by(user: user, tag: tag).notification_level).to eq(
TagUser.notification_levels[:watching],
)
end
it "publishes category updates for a single added user" do
category = Fabricate(:category)
group.update!(categories: [category])
message = MessageBus.track_publish("/categories") { group.bulk_add([user.id]) }.first
expect(message).to be_present
expect(message.user_ids).to eq([user.id])
end
it "publishes a refresh when adding multiple users" do
category = Fabricate(:category)
group.update!(categories: [category])
message =
MessageBus.track_publish("/refresh_client") { group.bulk_add([user.id, admin.id]) }.first
expect(message.user_ids).to contain_exactly(user.id, admin.id)
end
end
describe "#bulk_remove" do
before { group.bulk_add([user.id, admin.id]) }
it "removes multiple users and returns their IDs" do
result = group.bulk_remove([user.id, admin.id])
expect(result).to contain_exactly(user.id, admin.id)
expect(group.group_users.count).to eq(0)
end
it "returns empty array for blank input" do
expect(group.bulk_remove([])).to eq([])
expect(group.bulk_remove(nil)).to eq([])
end
it "updates group user count" do
group.reload
expect { group.bulk_remove([user.id]) }.to change { group.reload.user_count }.from(2).to(1)
end
it "ignores user_ids not in the group" do
result = group.bulk_remove([user.id, admin.id, admin.id + 1000])
expect(result).to contain_exactly(user.id, admin.id)
end
it "clears primary_group_id" do
group.update!(primary_group: true)
User.where(id: [user.id, admin.id]).update_all(primary_group_id: group.id)
group.bulk_remove([user.id])
expect(user.reload.primary_group_id).to be_nil
expect(admin.reload.primary_group_id).to eq(group.id)
end
it "clears flair_group_id" do
User.where(id: user.id).update_all(flair_group_id: group.id)
group.bulk_remove([user.id])
expect(user.reload.flair_group_id).to be_nil
end
context "when stripping title" do
before { group.update!(title: "Awesome") }
it "clears title when user has no other titled groups or badges" do
user.update!(title: "Awesome")
group.bulk_remove([user.id])
expect(user.reload.title).to be_nil
end
it "does not clear title if it doesn't match the group title" do
user.update!(title: "Something Else")
group.bulk_remove([user.id])
expect(user.reload.title).to eq("Something Else")
end
it "assigns title from remaining group when removed from current title group" do
other_group = Fabricate(:group, title: "Other Title")
other_group.bulk_add([user.id])
user.update!(title: "Awesome")
group.bulk_remove([user.id])
expect(user.reload.title).to eq("Other Title")
end
end
it "enqueues bulk_grant_trust_level job with recalculate flag" do
group.update!(grant_trust_level: 2)
expect_enqueued_with(
job: :bulk_grant_trust_level,
args: {
recalculate: true,
user_ids: [user.id],
},
) { group.bulk_remove([user.id]) }
end
it "triggers user_removed_from_group event for each user" do
events =
DiscourseEvent.track_events(:user_removed_from_group) do
group.bulk_remove([user.id, admin.id])
end
expect(events.size).to eq(2)
expect(events.map { |e| e[:params][0] }).to contain_exactly(user, admin)
end
it "publishes category updates for removed users" do
category = Fabricate(:category)
group.update!(categories: [category])
message =
MessageBus.track_publish("/refresh_client") { group.bulk_remove([user.id, admin.id]) }.first
expect(message.user_ids).to contain_exactly(user.id, admin.id)
end
describe "with webhook" do
fab!(:group_user_web_hook)
it "enqueues webhook events for each removed user" do
group.bulk_remove([user.id, admin.id])
webhook_jobs = Jobs::EmitWebHookEvent.jobs.last(2)
webhook_jobs.each do |event|
job_args = event["args"].first
expect(job_args["event_name"]).to eq("user_removed_from_group")
payload = JSON.parse(job_args["payload"])
expect(payload["group_id"]).to eq(group.id)
expect([user.id, admin.id]).to include(payload["user_id"])
end
end
end
end
it "Correctly updates has_messages" do
group = Fabricate(:group, has_messages: true)
topic = Fabricate(:private_message_topic)
# when group message is not present
Group.refresh_has_messages!
group.reload
expect(group.has_messages?).to eq false
# when group message is present
group.update!(has_messages: true)
TopicAllowedGroup.create!(topic_id: topic.id, group_id: group.id)
Group.refresh_has_messages!
group.reload
expect(group.has_messages?).to eq true
end
describe "#automatic_group_membership" do
let(:group) { Fabricate(:group, automatic_membership_email_domains: "example.com") }
it "should be triggered on create and update" do
expect { group }.to change { Jobs::AutomaticGroupMembership.jobs.size }.by(1)
job = Jobs::AutomaticGroupMembership.jobs.last
expect(job["args"].first["group_id"]).to eq(group.id)
Jobs::AutomaticGroupMembership.jobs.clear
expect do group.update!(name: "asdiaksjdias") end.to change {
Jobs::AutomaticGroupMembership.jobs.size
}.by(1)
job = Jobs::AutomaticGroupMembership.jobs.last
expect(job["args"].first["group_id"]).to eq(group.id)
end
end
describe "Unicode usernames and group names" do
before { SiteSetting.unicode_usernames = true }
it "should normalize the name" do
group = Fabricate(:group, name: "Bücherwurm") # NFD
expect(group.name).to eq("Bücherwurm") # NFC
end
end
describe "default notifications" do
let(:category1) { Fabricate(:category) }
let(:category2) { Fabricate(:category) }
let(:category3) { Fabricate(:category) }
let(:category4) { Fabricate(:category) }
let(:tag1) { Fabricate(:tag) }
let(:tag2) { Fabricate(:tag) }
let(:tag3) { Fabricate(:tag) }
let(:tag4) { Fabricate(:tag) }
let(:synonym1) { Fabricate(:tag, target_tag: tag1) }
let(:synonym2) { Fabricate(:tag, target_tag: tag2) }
it "can set category notifications" do
group.watching_category_ids = [category1.id, category2.id]
group.tracking_category_ids = [category3.id]
group.regular_category_ids = [category4.id]
group.save!
expect(
GroupCategoryNotificationDefault.lookup(group, :watching).pluck(:category_id),
).to contain_exactly(category1.id, category2.id)
expect(GroupCategoryNotificationDefault.lookup(group, :tracking).pluck(:category_id)).to eq(
[category3.id],
)
expect(GroupCategoryNotificationDefault.lookup(group, :regular).pluck(:category_id)).to eq(
[category4.id],
)
new_group = Fabricate.build(:group)
new_group.watching_category_ids = [category1.id, category2.id]
new_group.save!
expect(
GroupCategoryNotificationDefault.lookup(new_group, :watching).pluck(:category_id),
).to contain_exactly(category1.id, category2.id)
end
it "can remove categories" do
[category1, category2].each do |category|
GroupCategoryNotificationDefault.create!(
group: group,
category: category,
notification_level: GroupCategoryNotificationDefault.notification_levels[:watching],
)
end
group.watching_category_ids = [category2.id]
group.save!
expect(GroupCategoryNotificationDefault.lookup(group, :watching).pluck(:category_id)).to eq(
[category2.id],
)
group.watching_category_ids = []
group.save!
expect(
GroupCategoryNotificationDefault.lookup(group, :watching).pluck(:category_id),
).to be_empty
end
it "can set tag notifications" do
group.regular_tags = [tag4.name]
group.watching_tags = [tag1.name, tag2.name]
group.tracking_tags = [tag3.name]
group.save!
expect(GroupTagNotificationDefault.lookup(group, :regular).pluck(:tag_id)).to eq([tag4.id])
expect(
GroupTagNotificationDefault.lookup(group, :watching).pluck(:tag_id),
).to contain_exactly(tag1.id, tag2.id)
expect(GroupTagNotificationDefault.lookup(group, :tracking).pluck(:tag_id)).to eq([tag3.id])
new_group = Fabricate.build(:group)
new_group.watching_first_post_tags = [tag1.name, tag3.name]
new_group.save!
expect(
GroupTagNotificationDefault.lookup(new_group, :watching_first_post).pluck(:tag_id),
).to contain_exactly(tag1.id, tag3.id)
end
it "can take tag synonyms" do
group.tracking_tags = [synonym1.name, synonym2.name, tag3.name]
group.save!
expect(
GroupTagNotificationDefault.lookup(group, :tracking).pluck(:tag_id),
).to contain_exactly(tag1.id, tag2.id, tag3.id)
group.tracking_tags = [synonym1.name, synonym2.name, tag1.name, tag2.name, tag3.name]
group.save!
expect(
GroupTagNotificationDefault.lookup(group, :tracking).pluck(:tag_id),
).to contain_exactly(tag1.id, tag2.id, tag3.id)
end
it "can remove tags" do
[tag1, tag2].each do |tag|
GroupTagNotificationDefault.create!(
group: group,
tag: tag,
notification_level: GroupTagNotificationDefault.notification_levels[:watching],
)
end
group.watching_tags = [tag2.name]
group.save!
expect(GroupTagNotificationDefault.lookup(group, :watching).pluck(:tag_id)).to eq([tag2.id])
group.watching_tags = []
group.save!
expect(GroupTagNotificationDefault.lookup(group, :watching)).to be_empty
end
it "can change the notification level for a tag" do
GroupTagNotificationDefault.create!(
group: group,
tag: tag1,
notification_level: GroupTagNotificationDefault.notification_levels[:watching],
)
group.watching_tags = [tag1.name]
group.save!
expect(GroupTagNotificationDefault.lookup(group, :watching).pluck(:tag_id)).to eq([tag1.id])
group.watching_tags = []
group.tracking_tags = [tag1.name]
group.save!
expect(GroupTagNotificationDefault.lookup(group, :watching)).to be_empty
expect(GroupTagNotificationDefault.lookup(group, :tracking).pluck(:tag_id)).to eq([tag1.id])
end
it "can apply default notifications for admins group" do
group = Group.find(Group::AUTO_GROUPS[:admins])
group.tracking_category_ids = [category1.id]
group.tracking_tags = [tag1.name]
group.save!
user.grant_admin!
expect(CategoryUser.lookup(user, :tracking).pluck(:category_id)).to eq([category1.id])
expect(TagUser.lookup(user, :tracking).pluck(:tag_id)).to eq([tag1.id])
end
it "can apply default notifications for staff group" do
group = Group.find(Group::AUTO_GROUPS[:staff])
group.tracking_category_ids = [category1.id]
group.tracking_tags = [tag1.name]
group.save!
user.grant_admin!
expect(CategoryUser.lookup(user, :tracking).pluck(:category_id)).to eq([category1.id])
expect(TagUser.lookup(user, :tracking).pluck(:tag_id)).to eq([tag1.id])
end
it "can apply default notifications from two automatic groups" do
staff = Group.find(Group::AUTO_GROUPS[:staff])
staff.tracking_category_ids = [category1.id]
staff.tracking_tags = [tag1.name]
staff.save!
admins = Group.find(Group::AUTO_GROUPS[:admins])
admins.tracking_category_ids = [category2.id]
admins.tracking_tags = [tag2.name]
admins.save!
user.grant_admin!
expect(CategoryUser.lookup(user, :tracking).pluck(:category_id)).to contain_exactly(
category1.id,
category2.id,
)
expect(TagUser.lookup(user, :tracking).pluck(:tag_id)).to contain_exactly(tag1.id, tag2.id)
end
end
describe "email setting changes" do
it "enables smtp and records the change" do
group.update(
smtp_port: 587,
smtp_ssl_mode: Group.smtp_ssl_modes[:starttls],
smtp_server: "smtp.gmail.com",
email_username: "test@gmail.com",
email_password: "password",
)
group.record_email_setting_changes!(user)
group.reload
expect(group.smtp_enabled).to eq(true)
expect(group.smtp_updated_at).not_to eq(nil)
expect(group.smtp_updated_by).to eq(user)
end
it "records the change for singular setting changes" do
group.update(
smtp_port: 587,
smtp_ssl_mode: Group.smtp_ssl_modes[:starttls],
smtp_server: "smtp.gmail.com",
email_username: "test@gmail.com",
email_password: "password",
)
group.record_email_setting_changes!(user)
group.reload
old_updated_at = group.smtp_updated_at
group.update(email_from_alias: "somealias@gmail.com")
group.record_email_setting_changes!(user)
expect(group.reload.smtp_updated_at).not_to eq_time(old_updated_at)
end
it "disables smtp and records the change" do
group.update(
smtp_port: 587,
smtp_ssl_mode: Group.smtp_ssl_modes[:starttls],
smtp_server: "smtp.gmail.com",
email_username: "test@gmail.com",
email_password: "password",
smtp_updated_by: user,
)
group.record_email_setting_changes!(user)
group.reload
group.update(
smtp_port: nil,
smtp_ssl_mode: Group.smtp_ssl_modes[:none],
smtp_server: nil,
email_username: nil,
email_password: nil,
)
group.record_email_setting_changes!(user)
group.reload
expect(group.smtp_enabled).to eq(false)
expect(group.smtp_updated_at).not_to eq(nil)
expect(group.smtp_updated_by).to eq(user)
end
end
describe "#find_by_email" do
it "finds the group by any of its incoming emails" do
group.update!(incoming_email: "abc@test.com|support@test.com")
expect(Group.find_by_email("abc@test.com")).to eq(group)
expect(Group.find_by_email("support@test.com")).to eq(group)
expect(Group.find_by_email("nope@test.com")).to eq(nil)
end
it "finds the group by its email_username" do
group.update!(email_username: "abc@test.com", incoming_email: "support@test.com")
expect(Group.find_by_email("abc@test.com")).to eq(group)
expect(Group.find_by_email("support@test.com")).to eq(group)
expect(Group.find_by_email("nope@test.com")).to eq(nil)
end
it "finds the group by its email_from_alias" do
group.update!(email_username: "abc@test.com", email_from_alias: "somealias@test.com")
expect(Group.find_by_email("abc@test.com")).to eq(group)
expect(Group.find_by_email("somealias@test.com")).to eq(group)
expect(Group.find_by_email("nope@test.com")).to eq(nil)
end
end
context "when a group is deleted" do
it "enqueues a job to update the associated access_control_list records" do
expect_enqueued_with(job: :cleanup_acls_for_deleted, args: { group_id: group.id }) do
group.destroy!
end
end
end
end