0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-11 02:59:07 +08:00
discourse/spec/serializers/site_serializer_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

724 lines
25 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe SiteSerializer do
let(:guardian) { Guardian.new }
let(:category) { Fabricate(:category) }
after { Site.clear_cache }
describe "category_types" do
it "is included on each serialized category in the site payload" do
category
Site.clear_cache
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
c = serialized[:categories].find { |entry| entry[:id] == category.id }
expect(c).to have_key(:category_types)
end
end
describe "#user_tips" do
it "is included if enable_user_tips" do
SiteSetting.enable_user_tips = true
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:user_tips]).to eq(User.user_tips)
end
it "is not included if enable_user_tips is disabled" do
SiteSetting.enable_user_tips = false
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:user_tips]).to eq(nil)
end
end
describe "#access_control" do
let(:target_class) do
Class.new(ActiveRecord::Base) do
include AclTarget
self.table_name = "posts"
def self.name
"SiteSerializerSpecTarget"
end
def self.mandatory_acl
[{ type: :group, id: Group::AUTO_GROUPS[:admins], permission: "manage" }]
end
def self.banned_acl
[{ type: :group, id: Group::AUTO_GROUPS[:anonymous_users], permission: "edit" }]
end
end
end
after { DiscoursePluginRegistry.reset_register!(:acl_target_classes) }
it "includes mandatory ACLs by target class" do
target_class
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized.dig(:access_control, :mandatory_acl)).to include(
"SiteSerializerSpecTarget" => [
{ type: :group, id: Group::AUTO_GROUPS[:admins], permission: "manage" },
],
)
end
it "includes banned ACLs by target class" do
target_class
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized.dig(:access_control, :banned_acl)).to include(
"SiteSerializerSpecTarget" => [
{ type: :group, id: Group::AUTO_GROUPS[:anonymous_users], permission: "edit" },
],
)
end
it "includes plugin-registered target classes" do
Object.const_set(:SiteSerializerSpecTarget, target_class)
AclTarget.loaded_target_classes.delete(target_class)
DiscoursePluginRegistry.register_acl_target_class(
"SiteSerializerSpecTarget",
Plugin::Instance.new,
)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized.dig(:access_control, :mandatory_acl)).to include(
"SiteSerializerSpecTarget" => [
{ type: :group, id: Group::AUTO_GROUPS[:admins], permission: "manage" },
],
)
ensure
Object.send(:remove_const, :SiteSerializerSpecTarget) if defined?(SiteSerializerSpecTarget)
end
end
it "includes category custom fields only if its preloaded" do
category.custom_fields["enable_marketplace"] = true
category.save_custom_fields
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
c1 = serialized[:categories].find { |c| c[:id] == category.id }
expect(c1[:custom_fields]).to eq(nil)
Site.preloaded_category_custom_fields << "enable_marketplace"
Site.clear_cache
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
c1 = serialized[:categories].find { |c| c[:id] == category.id }
expect(c1[:custom_fields]["enable_marketplace"]).to eq("t")
ensure
Site.reset_preloaded_category_custom_fields
end
it "includes category tags" do
tag = Fabricate(:tag)
tag_group = Fabricate(:tag_group)
tag_group_2 = Fabricate(:tag_group)
category.tags << tag
category.tag_groups << tag_group
category.update!(
category_required_tag_groups: [
CategoryRequiredTagGroup.new(tag_group: tag_group_2, min_count: 1),
],
)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
c1 = serialized[:categories].find { |c| c[:id] == category.id }
expect(c1[:allowed_tags]).to contain_exactly({ id: tag.id, name: tag.name, slug: tag.slug })
expect(c1[:allowed_tag_groups]).to contain_exactly(tag_group.name)
expect(c1[:required_tag_groups]).to eq([{ name: tag_group_2.name, min_count: 1 }])
end
it "doesn't explode when category_required_tag_group is missing" do
tag = Fabricate(:tag)
tag_group = Fabricate(:tag_group)
crtg = CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1)
category.update!(category_required_tag_groups: [crtg])
tag_group.delete # Bypassing hooks like this should never happen in the app
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
c1 = serialized[:categories].find { |c| c[:id] == category.id }
expect(c1[:required_tag_groups]).to eq([{ name: nil, min_count: 1 }])
end
it "returns correct notification level for categories" do
SiteSetting.mute_all_categories_by_default = true
SiteSetting.default_categories_normal = category.id.to_s
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
categories = serialized[:categories]
expect(categories[0][:notification_level]).to eq(0)
expect(categories[-1][:notification_level]).to eq(1)
end
it "includes user-selectable color schemes" do
dark_scheme =
ColorScheme.create_from_base(
name: "AnotherDarkScheme",
base_scheme_id: ColorScheme::NAMES_TO_ID_MAP["Dark"],
)
dark_scheme.user_selectable = true
dark_scheme.save!
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:user_color_schemes].count).to eq(1)
expect(serialized[:user_color_schemes][0][:is_dark]).to eq(true)
end
describe "only_theme_color_schemes modifier" do
fab!(:theme)
fab!(:color_scheme) { Fabricate(:color_scheme, theme_id: theme.id, user_selectable: false) }
before do
theme.update!(user_selectable: true)
theme.theme_modifier_set.update!(only_theme_color_schemes: true)
end
it "includes theme color schemes in user_color_schemes when modifier is active" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
scheme_ids = serialized[:user_color_schemes].map { |s| s[:id] }
expect(scheme_ids).to include(color_scheme.id)
end
it "includes only_theme_color_schemes flag in user_themes" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
theme_data = serialized[:user_themes].find { |t| t["theme_id"] == theme.id }
expect(theme_data["only_theme_color_schemes"]).to eq(true)
end
end
it "includes default dark mode scheme" do
scheme = ColorScheme.last
Theme.find_default.update!(dark_color_scheme_id: scheme.id)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
default_dark_scheme = expect(serialized[:default_dark_color_scheme][:name]).to eq(scheme.name)
Theme.find_default.update!(dark_color_scheme_id: nil)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:default_dark_color_scheme]).to eq(nil)
end
it "does not include shared_drafts_category_id if the category is Uncategorized" do
admin = Fabricate(:admin)
admin_guardian = Guardian.new(admin)
SiteSetting.shared_drafts_category = SiteSetting.uncategorized_category_id
serialized =
described_class.new(Site.new(admin_guardian), scope: admin_guardian, root: false).as_json
expect(serialized[:shared_drafts_category_id]).to eq(nil)
end
context "with lazy loaded categories enabled" do
fab!(:user)
fab!(:category)
fab!(:sidebar) { Fabricate(:category_sidebar_section_link, linkable: category, user: user) }
before { SiteSetting.lazy_load_categories_groups = "#{Group::AUTO_GROUPS[:logged_in_users]}" }
it "includes categories for anonymous users" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:categories].map { |category| category[:id] }).to include(category.id)
end
it "includes preloaded categories for logged in users" do
guardian = Guardian.new(user)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:categories].map { |c| c[:id] }).to contain_exactly(category.id)
end
end
describe "#anonymous_default_navigation_menu_tags" do
fab!(:user)
fab!(:tag) { Fabricate(:tag, name: "dev", description: "some description") }
fab!(:tag2) { Fabricate(:tag, name: "random") }
fab!(:hidden_tag) { Fabricate(:tag, name: "secret") }
fab!(:staff_tag_group) do
Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name])
end
before do
SiteSetting.navigation_menu = "sidebar"
SiteSetting.tagging_enabled = true
SiteSetting.default_navigation_menu_tags = "#{tag.name}|#{tag2.name}|#{hidden_tag.name}"
end
it "is not included in the serialised object when tagging is not enabled" do
SiteSetting.tagging_enabled = false
guardian = Guardian.new(user)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_default_navigation_menu_tags]).to eq(nil)
end
it "is not included in the serialised object when user is not anonymous" do
guardian = Guardian.new(user)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_default_navigation_menu_tags]).to eq(nil)
end
it "is not included in the serialisd object when default sidebar tags have not been configured" do
SiteSetting.default_navigation_menu_tags = ""
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_default_navigation_menu_tags]).to eq(nil)
end
it "includes only tags user can see in the serialised object when user is anonymous" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_default_navigation_menu_tags]).to eq(
[
{
id: tag.id,
name: "dev",
slug: tag.slug,
description: "some description",
pm_only: false,
},
{
id: tag2.id,
name: "random",
slug: tag2.slug,
description: tag2.description,
pm_only: false,
},
],
)
end
end
describe "#anonymous_sidebar_sections" do
fab!(:user)
fab!(:public_sidebar_section) do
Fabricate(:sidebar_section, title: "Public section", public: true)
end
fab!(:private_sidebar_section) do
Fabricate(:sidebar_section, title: "Private section", user: user, public: false)
end
it "is not included in the serialised object when user is not anonymous" do
guardian = Guardian.new(user)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized).not_to have_key(:anonymous_sidebar_sections)
end
it "includes only public sidebar sections serialised object when user is anonymous" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_sidebar_sections].map { |section| section[:title] }).to eq(
["Community", "Public section"],
)
end
it "eager loads sidebar_urls" do
public_section_link =
Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: public_sidebar_section)
# warmup
described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
initial_count =
track_sql_queries do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_sidebar_sections].count).to eq(2)
expect(serialized[:anonymous_sidebar_sections].last[:links].map { |link| link.id }).to eq(
[public_section_link.linkable.id],
)
end.count
public_section_link_2 =
Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: public_sidebar_section)
public_section_link_3 =
Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: public_sidebar_section)
final_count =
track_sql_queries do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_sidebar_sections].count).to eq(2)
expect(serialized[:anonymous_sidebar_sections].last[:links].map { |link| link.id }).to eq(
[
public_section_link.linkable.id,
public_section_link_2.linkable.id,
public_section_link_3.linkable.id,
],
)
end.count
expect(final_count).to eq(initial_count)
end
end
describe "#top_tags" do
fab!(:tag)
describe "when tagging is not enabled" do
before { SiteSetting.tagging_enabled = false }
it "is not included in the serialised object" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:top_tags]).to eq(nil)
end
end
describe "when tagging is enabled" do
fab!(:tag2, :tag)
fab!(:tag3, :tag)
before { SiteSetting.tagging_enabled = true }
it "is not included in the serialised object when there are no tags" do
tag.destroy!
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:top_tags]).to eq([])
end
it "is included in the serialised object containing the top tags" do
tag2 = Fabricate(:tag)
tag2 = Fabricate(:tag)
SiteSetting.max_tags_in_filter_list = 1
CategoryTagStat.create!(
category_id: SiteSetting.uncategorized_category_id,
tag_id: tag2.id,
topic_count: 2,
)
CategoryTagStat.create!(
category_id: SiteSetting.uncategorized_category_id,
tag_id: tag.id,
topic_count: 1,
)
CategoryTagStat.create!(
category_id: SiteSetting.uncategorized_category_id,
tag_id: tag3.id,
topic_count: 5,
)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:top_tags]).to eq(
[
{ id: tag3.id, name: tag3.name, slug: tag3.slug },
{ id: tag2.id, name: tag2.name, slug: tag2.slug },
],
)
end
end
end
describe "#navigation_menu_site_top_tags" do
fab!(:tag1) do
Fabricate(:tag, name: "tag 1").tap { |tag| Fabricate.times(2, :topic, tags: [tag]) }
end
fab!(:tag2) do
Fabricate(:tag, name: "tag 2").tap { |tag| Fabricate.times(1, :topic, tags: [tag]) }
end
fab!(:tag3) do
Fabricate(:tag, name: "tag 3").tap { |tag| Fabricate.times(3, :topic, tags: [tag]) }
end
fab!(:hidden_tag) do
Fabricate(:tag, name: "tag 4").tap { |tag| Fabricate.times(4, :topic, tags: [tag]) }
end
fab!(:staff_tag_group) do
Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name])
end
it "should return the site's top tags as the default tags for sidebar" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:navigation_menu_site_top_tags]).to eq(
[
{
id: tag3.id,
name: tag3.name,
slug: tag3.slug_for_url,
description: tag3.description,
pm_only: false,
},
{
id: tag1.id,
name: tag1.name,
slug: tag1.slug_for_url,
description: tag1.description,
pm_only: false,
},
{
id: tag2.id,
name: tag2.name,
slug: tag2.slug_for_url,
description: tag2.description,
pm_only: false,
},
],
)
end
it "should not be serialized if `tagging_enabled` site setting is set to false" do
SiteSetting.set(:tagging_enabled, false)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:navigation_menu_site_top_tags]).to eq(nil)
end
it "should use slug_for_url for tags with empty slugs" do
numeric_tag =
Fabricate(:tag, name: "1").tap { |tag| Fabricate.times(10, :topic, tags: [tag]) }
expect(numeric_tag.slug).to eq("")
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
numeric_entry =
serialized[:navigation_menu_site_top_tags].find { |t| t[:id] == numeric_tag.id }
expect(numeric_entry[:slug]).to eq("#{numeric_tag.id}-tag")
end
it "should return an empty array if site has no top tags" do
Tag.delete_all
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:navigation_menu_site_top_tags]).to eq([])
end
end
describe "#whispers_allowed_groups_names" do
fab!(:admin)
fab!(:allowed_user, :user)
fab!(:not_allowed_user, :user)
fab!(:group1) { Fabricate(:group, name: "whisperers1", users: [allowed_user]) }
fab!(:group2) { Fabricate(:group, name: "whisperers2", users: [allowed_user]) }
it "returns correct group names for created groups" do
admin_guardian = Guardian.new(admin)
SiteSetting.whispers_allowed_groups = "#{group1.id}|#{group2.id}"
serialized =
described_class.new(Site.new(admin_guardian), scope: admin_guardian, root: false).as_json
expect(serialized[:whispers_allowed_groups_names]).to contain_exactly(
"whisperers1",
"whisperers2",
)
end
it "returns correct group names for automatic groups" do
admin_guardian = Guardian.new(admin)
SiteSetting.whispers_allowed_groups =
"#{Group::AUTO_GROUPS[:staff]}|#{Group::AUTO_GROUPS[:trust_level_4]}"
serialized =
described_class.new(Site.new(admin_guardian), scope: admin_guardian, root: false).as_json
expect(serialized[:whispers_allowed_groups_names]).to contain_exactly(
"trust_level_4",
"staff",
)
end
it "returns group names when user is allowed to whisper" do
user_guardian = Guardian.new(allowed_user)
SiteSetting.whispers_allowed_groups = "#{group1.id}|#{group2.id}"
serialized =
described_class.new(Site.new(user_guardian), scope: user_guardian, root: false).as_json
expect(serialized[:whispers_allowed_groups_names]).to contain_exactly(
"whisperers1",
"whisperers2",
)
end
it "returns nil when user is not allowed to whisper" do
user_guardian = Guardian.new(not_allowed_user)
SiteSetting.whispers_allowed_groups =
"#{Group::AUTO_GROUPS[:staff]}|#{Group::AUTO_GROUPS[:trust_level_4]}"
serialized =
described_class.new(Site.new(user_guardian), scope: user_guardian, root: false).as_json
expect(serialized[:whispers_allowed_groups_names]).to eq(nil)
end
end
describe "#full_name_required_for_signup" do
let(:site_json) do
described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
end
it "is false when full_name_requirement setting is optional_at_signup" do
SiteSetting.full_name_requirement = "optional_at_signup"
expect(site_json[:full_name_required_for_signup]).to eq(false)
end
it "is false when full_name_requirement setting is hidden_at_signup" do
SiteSetting.full_name_requirement = "hidden_at_signup"
SiteSetting.enable_names = true
expect(site_json[:full_name_required_for_signup]).to eq(false)
end
it "is true when full_name_requirement setting is required_at_signup and enable_names is true" do
SiteSetting.full_name_requirement = "required_at_signup"
SiteSetting.enable_names = true
expect(site_json[:full_name_required_for_signup]).to eq(true)
end
end
describe "#full_name_visible_in_signup" do
let(:site_json) do
described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
end
it "is false when full_name_requirement is hidden_at_signup" do
SiteSetting.full_name_requirement = "hidden_at_signup"
expect(site_json[:full_name_visible_in_signup]).to eq(false)
end
it "is true when enable_names setting is true and full_name_requirement is optional_at_signup" do
SiteSetting.full_name_requirement = "optional_at_signup"
expect(site_json[:full_name_visible_in_signup]).to eq(true)
end
it "is true when enable_names setting is true and full_name_requirement is required_at_signup" do
SiteSetting.full_name_requirement = "required_at_signup"
expect(site_json[:full_name_visible_in_signup]).to eq(true)
end
it "is false when enable_names setting is true and full_name_requirement is hidden_at_signup" do
SiteSetting.full_name_requirement = "hidden_at_signup"
expect(site_json[:full_name_visible_in_signup]).to eq(false)
end
end
describe "#email_configured" do
it "returns true when smtp_address is set" do
global_setting :smtp_address, "smtp.example.com"
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:email_configured]).to eq(true)
end
it "returns false when smtp_address is blank" do
global_setting :smtp_address, ""
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:email_configured]).to eq(false)
end
end
describe "#groups" do
fab!(:group)
fab!(:admin)
it "serializes the automatic field of each group" do
serialized_groups =
described_class.new(Site.new(admin.guardian), scope: admin.guardian, root: false).as_json[
:groups
]
expect(serialized_groups.find { |g| g["name"] == "trust_level_1" }["automatic"]).to eq(true)
expect(serialized_groups.find { |g| g["name"] == group.name }["automatic"]).to eq(false)
end
end
describe "#admin_config_login_routes" do
fab!(:admin)
fab!(:user)
before { DiscoursePluginRegistry.admin_config_login_routes << "plugin_login_route" }
after { DiscoursePluginRegistry.admin_config_login_routes.delete("plugin_login_route") }
it "is included for admin users" do
admin_guardian = Guardian.new(admin)
serialized =
described_class.new(Site.new(admin_guardian), scope: admin_guardian, root: false).as_json
expect(serialized[:admin_config_login_routes]).to include("plugin_login_route")
end
it "is not included for anonymous users" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized).not_to have_key(:admin_config_login_routes)
end
it "is not included for non-admin users" do
user_guardian = Guardian.new(user)
serialized =
described_class.new(Site.new(user_guardian), scope: user_guardian, root: false).as_json
expect(serialized).not_to have_key(:admin_config_login_routes)
end
end
describe "#upcoming_changes_with_css" do
it "returns upcoming changes with body_class: true" do
mock_upcoming_change_metadata(
{
enable_upload_debug_mode: {
impact: "other,developers",
status: :beta,
impact_type: "other",
impact_role: "developers",
body_class: true,
},
enable_user_tips: {
impact: "feature,all_members",
status: :alpha,
impact_type: "feature",
impact_role: "all_members",
body_class: false,
},
},
)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:upcoming_changes_with_css]).to include(:enable_upload_debug_mode)
expect(serialized[:upcoming_changes_with_css]).not_to include(:enable_user_tips)
end
end
describe "#permanent_upcoming_change_names" do
before do
UpcomingChanges.stubs(:permanent_upcoming_change_names).returns(%w[enable_permanent_feature])
end
it "returns the permanent change names for staff" do
admin = Fabricate(:admin)
admin_guardian = Guardian.new(admin)
serialized =
described_class.new(Site.new(admin_guardian), scope: admin_guardian, root: false).as_json
expect(serialized[:permanent_upcoming_change_names]).to eq(%w[enable_permanent_feature])
end
it "is not included for non-staff users" do
user = Fabricate(:user)
user_guardian = Guardian.new(user)
serialized =
described_class.new(Site.new(user_guardian), scope: user_guardian, root: false).as_json
expect(serialized).not_to have_key(:permanent_upcoming_change_names)
end
end
end