discourse/spec/serializers/current_user_serializer_spec.rb
Martin Brennan 9a6fe5c370
FIX: Do not show new upcoming change dot on new sites and new staff (#39682)
When a site is created, any staff users created in that
period should not see a blue dot for the upcoming change
link in the admin sidebar, we don't need to notify them
of "new" upcoming changes when they have only just joined.

Additionally, any new admins or moderators created later
on should also not immediately see a blue dot in the
upcoming change sidebar, though these changes are "new"
to them, they could potentially be quite old upcoming changes.
2026-05-05 12:02:08 +10:00

551 lines
16 KiB
Ruby

# frozen_string_literal: true
RSpec.describe CurrentUserSerializer do
fab!(:user)
subject(:serializer) { described_class.new(user, scope: guardian, root: false) }
let(:guardian) { user.guardian }
context "when SSO is not enabled" do
it "should not include the external_id field" do
payload = serializer.as_json
expect(payload).not_to have_key(:external_id)
end
end
context "when SSO is enabled" do
let :user do
user = Fabricate(:user)
SingleSignOnRecord.create!(user_id: user.id, external_id: "12345", last_payload: "")
user
end
it "should include the external_id" do
SiteSetting.discourse_connect_url = "http://example.com/discourse_sso"
SiteSetting.discourse_connect_secret = "12345678910"
SiteSetting.enable_discourse_connect = true
payload = serializer.as_json
expect(payload[:external_id]).to eq("12345")
end
end
describe "#top_category_ids" do
fab!(:category1, :category)
fab!(:category2, :category)
fab!(:category3, :category)
it "should include empty top_category_ids array" do
payload = serializer.as_json
expect(payload[:top_category_ids]).to eq([])
end
it "should include correct id in top_category_ids array" do
_category = Category.first
CategoryUser.create!(
user_id: user.id,
category_id: category1.id,
notification_level: CategoryUser.notification_levels[:tracking],
)
CategoryUser.create!(
user_id: user.id,
category_id: category2.id,
notification_level: CategoryUser.notification_levels[:watching],
)
CategoryUser.create!(
user_id: user.id,
category_id: category3.id,
notification_level: CategoryUser.notification_levels[:regular],
)
payload = serializer.as_json
expect(payload[:top_category_ids]).to eq([category2.id, category1.id])
end
end
describe "#muted_tag" do
fab!(:tag)
let!(:tag_user) do
TagUser.create!(
user_id: user.id,
notification_level: TagUser.notification_levels[:muted],
tag_id: tag.id,
)
end
it "includes muted tags" do
payload = serializer.as_json
expect(payload[:muted_tags]).to eq([{ id: tag.id, name: tag.name, slug: tag.slug }])
end
end
describe "#second_factor_enabled" do
let(:guardian) { user.guardian }
let(:json) { serializer.as_json }
it "is false by default" do
expect(json[:second_factor_enabled]).to eq(false)
end
context "when totp enabled" do
before { User.any_instance.stubs(:totp_enabled?).returns(true) }
it "is true" do
expect(json[:second_factor_enabled]).to eq(true)
end
end
context "when security_keys enabled" do
before { User.any_instance.stubs(:security_keys_enabled?).returns(true) }
it "is true" do
expect(json[:second_factor_enabled]).to eq(true)
end
end
end
describe "#groups" do
it "should only show visible groups" do
Fabricate(:group, visibility_level: Group.visibility_levels[:public])
hidden_group = Fabricate(:group, visibility_level: Group.visibility_levels[:owners])
public_group =
Fabricate(
:group,
visibility_level: Group.visibility_levels[:public],
name: "UppercaseGroupName",
)
hidden_group.add(user)
public_group.add(user)
payload = serializer.as_json
expect(payload[:groups]).to contain_exactly(
{ id: public_group.id, name: public_group.name, has_messages: false },
)
end
end
describe "#can_ignore_users" do
let(:guardian) { user.guardian }
let(:payload) { serializer.as_json }
context "when user is a regular one" do
let(:user) { Fabricate(:user) }
it "return false for regular users" do
expect(payload[:can_ignore_users]).to eq(false)
end
end
context "when user is a staff member" do
let(:user) { Fabricate(:moderator) }
it "returns true" do
expect(payload[:can_ignore_users]).to eq(true)
end
end
end
describe "#can_review" do
let(:guardian) { user.guardian }
let(:payload) { serializer.as_json }
context "when user is a regular one" do
let(:user) { Fabricate(:user) }
it "return false for regular users" do
expect(payload[:can_review]).to eq(false)
end
end
context "when user is a staff member" do
let(:user) { Fabricate(:admin) }
it "returns true" do
expect(payload[:can_review]).to eq(true)
end
end
end
describe "#pending_posts_count" do
subject(:pending_posts_count) { serializer.pending_posts_count }
let(:user) { Fabricate(:user) }
before { user.user_stat.pending_posts_count = 3 }
it "serializes 'pending_posts_count'" do
expect(pending_posts_count).to eq 3
end
end
describe "#status" do
fab!(:user_status)
fab!(:user) { Fabricate(:user, user_status: user_status) }
let(:serializer) { described_class.new(user, scope: user.guardian, root: false) }
it "adds user status when enabled" do
SiteSetting.enable_user_status = true
json = serializer.as_json
expect(json[:status]).to_not be_nil do |status|
expect(status.description).to eq(user_status.description)
expect(status.emoji).to eq(user_status.emoji)
end
end
it "doesn't add user status when disabled" do
SiteSetting.enable_user_status = false
json = serializer.as_json
expect(json.keys).not_to include :status
end
it "doesn't add expired user status" do
SiteSetting.enable_user_status = true
user.user_status.ends_at = 1.minute.ago
serializer = described_class.new(user, scope: user.guardian, root: false)
json = serializer.as_json
expect(json.keys).not_to include :status
end
it "doesn't return status if user doesn't have it set" do
SiteSetting.enable_user_status = true
user.clear_status!
user.reload
json = serializer.as_json
expect(json.keys).not_to include :status
end
end
describe "#likes_notifications_disabled" do
it "is true if the user disables likes notifications" do
user.user_option.update!(
like_notification_frequency: UserOption.like_notification_frequency_type[:never],
)
expect(serializer.as_json[:user_option][:likes_notifications_disabled]).to eq(true)
end
it "is false if the user doesn't disable likes notifications" do
user.user_option.update!(
like_notification_frequency: UserOption.like_notification_frequency_type[:always],
)
expect(serializer.as_json[:user_option][:likes_notifications_disabled]).to eq(false)
user.user_option.update!(
like_notification_frequency:
UserOption.like_notification_frequency_type[:first_time_and_daily],
)
expect(serializer.as_json[:user_option][:likes_notifications_disabled]).to eq(false)
user.user_option.update!(
like_notification_frequency: UserOption.like_notification_frequency_type[:first_time],
)
expect(serializer.as_json[:user_option][:likes_notifications_disabled]).to eq(false)
end
end
describe "#associated_account_ids" do
before do
UserAssociatedAccount.create(
user_id: user.id,
provider_name: "twitter",
provider_uid: "1",
info: {
nickname: "sam",
},
)
end
it "should not include associated account ids by default" do
expect(serializer.as_json[:associated_account_ids]).to be_nil
end
it "should include associated account ids when site setting enabled" do
SiteSetting.include_associated_account_ids = true
expect(serializer.as_json[:associated_account_ids]).to eq({ "twitter" => "1" })
end
end
describe "#new_personal_messages_notifications_count" do
fab!(:notification) do
Fabricate(
:notification,
user: user,
read: false,
notification_type: Notification.types[:private_message],
)
end
it "is included when sidebar is enabled" do
SiteSetting.navigation_menu = "sidebar"
expect(serializer.as_json[:new_personal_messages_notifications_count]).to eq(1)
end
end
include_examples "User Sidebar Serializer Attributes", described_class
describe "#sidebar_sections" do
fab!(:group)
fab!(:sidebar_section) { Fabricate(:sidebar_section, user: user) }
it "eager loads sidebar_urls" do
custom_sidebar_section_link_1 =
Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: sidebar_section)
# warmup
described_class.new(user, scope: user.guardian, root: false).as_json
initial_count =
track_sql_queries do
serialized = described_class.new(user, scope: user.guardian, root: false).as_json
expect(serialized[:sidebar_sections].count).to eq(2)
expect(serialized[:sidebar_sections].last[:links].map { |link| link.id }).to eq(
[custom_sidebar_section_link_1.linkable.id],
)
end.count
custom_sidebar_section_link_2 =
Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: sidebar_section)
final_count =
track_sql_queries do
serialized = described_class.new(user, scope: user.guardian, root: false).as_json
expect(serialized[:sidebar_sections].count).to eq(2)
expect(serialized[:sidebar_sections].last[:links].map { |link| link.id }).to eq(
[custom_sidebar_section_link_1.linkable.id, custom_sidebar_section_link_2.linkable.id],
)
end.count
expect(initial_count).to eq(final_count)
end
end
describe "#can_create_category" do
let(:payload) { serializer.as_json }
context "when user is an admin" do
let(:user) { Fabricate(:admin) }
it "returns true" do
expect(payload[:can_create_category]).to eq(true)
end
end
context "when user is a moderator and moderators_manage_categories is enabled" do
let(:user) { Fabricate(:moderator) }
before { SiteSetting.moderators_manage_categories = true }
it "returns true" do
expect(payload[:can_create_category]).to eq(true)
end
end
context "when user is a moderator and moderators_manage_categories is disabled" do
let(:user) { Fabricate(:moderator) }
before { SiteSetting.moderators_manage_categories = false }
it "is not included" do
expect(payload).not_to have_key(:can_create_category)
end
end
context "when user is a regular user" do
it "is not included" do
expect(payload).not_to have_key(:can_create_category)
end
end
end
describe "#can_see_ip" do
let(:payload) { serializer.as_json }
context "when user is an admin" do
let(:user) { Fabricate(:admin) }
it "includes can_see_ip as true" do
expect(payload[:can_see_ip]).to eq(true)
end
end
context "when user is a moderator and moderators_view_ips is enabled" do
let(:user) { Fabricate(:moderator) }
before { SiteSetting.moderators_view_ips = true }
it "includes can_see_ip as true" do
expect(payload[:can_see_ip]).to eq(true)
end
end
context "when user is a moderator and moderators_view_ips is disabled" do
let(:user) { Fabricate(:moderator) }
before { SiteSetting.moderators_view_ips = false }
it "does not include can_see_ip" do
expect(payload).not_to have_key(:can_see_ip)
end
end
end
describe "#featured_topic" do
fab!(:featured_topic, :topic)
before { user.user_profile.update!(featured_topic_id: featured_topic.id) }
it "includes the featured topic" do
payload = serializer.as_json
expect(payload[:featured_topic]).to_not be_nil
expect(payload[:featured_topic][:id]).to eq(featured_topic.id)
expect(payload[:featured_topic][:title]).to eq(featured_topic.title)
expect(payload[:featured_topic].keys).to contain_exactly(
:id,
:title,
:fancy_title,
:slug,
:posts_count,
)
end
end
describe "#show_site_owner_onboarding" do
fab!(:admin)
fab!(:topic)
fab!(:another_admin, :admin)
let(:admin_serializer) { described_class.new(admin, scope: Guardian.new(admin), root: false) }
before { SiteSetting.enable_site_owner_onboarding = true }
it "is not included for non-admin users" do
payload = serializer.as_json
expect(payload).not_to have_key(:show_site_owner_onboarding)
end
it "is not included when setting is disabled" do
SiteSetting.enable_site_owner_onboarding = false
payload = admin_serializer.as_json
expect(payload).not_to have_key(:show_site_owner_onboarding)
end
it "is true for the first admin on a new site" do
payload = admin_serializer.as_json
expect(payload[:show_site_owner_onboarding]).to eq(true)
end
it "is not included for a second admin" do
serializer2 =
described_class.new(another_admin, scope: Guardian.new(another_admin), root: false)
payload = serializer2.as_json
expect(payload).not_to have_key(:show_site_owner_onboarding)
end
it "is not included when the site is older than the max days setting" do
SiteSetting.site_owner_onboarding_max_days = 5
Topic.update_all(created_at: 6.days.ago)
payload = admin_serializer.as_json
expect(payload).not_to have_key(:show_site_owner_onboarding)
end
end
describe "#can_toggle_nested_mode" do
it "is not included when nested replies is disabled" do
SiteSetting.nested_replies_enabled = false
expect(serializer.as_json).not_to have_key(:can_toggle_nested_mode)
end
context "when nested replies is enabled" do
before { SiteSetting.nested_replies_enabled = true }
it "is true when user is in an allowed group" do
SiteSetting.nested_replies_toggle_mode_groups = Group::AUTO_GROUPS[:staff].to_s
user.update!(admin: true)
Group.refresh_automatic_groups!
user.reload
expect(serializer.as_json[:can_toggle_nested_mode]).to eq(true)
end
it "is false when user is not in an allowed group" do
SiteSetting.nested_replies_toggle_mode_groups = Group::AUTO_GROUPS[:staff].to_s
expect(serializer.as_json[:can_toggle_nested_mode]).to eq(false)
end
end
end
describe "#has_new_upcoming_changes" do
def serialize_for(user)
described_class.new(user, scope: user.guardian, root: false).as_json
end
it "is false for an admin created during the new-site window, regardless of `added` events" do
Discourse.stubs(:site_creation_date).returns(10.minutes.ago)
admin = Fabricate(:admin)
UpcomingChangeEvent.create!(
event_type: :added,
upcoming_change_name: "some_setting",
created_at: 5.minutes.ago,
)
UpcomingChangeEvent.create!(
event_type: :added,
upcoming_change_name: "future_setting",
created_at: 1.minute.from_now,
)
expect(serialize_for(admin)[:has_new_upcoming_changes]).to eq(false)
end
it "ignores `added` events that predate the user when no last_visited is set" do
Discourse.stubs(:site_creation_date).returns(2.years.ago)
UpcomingChangeEvent.create!(
event_type: :added,
upcoming_change_name: "old_setting",
created_at: 1.year.ago,
)
admin = Fabricate(:admin)
expect(serialize_for(admin)[:has_new_upcoming_changes]).to eq(false)
end
it "shows `added` events created after the user when no last_visited is set" do
Discourse.stubs(:site_creation_date).returns(2.years.ago)
admin = Fabricate(:admin)
UpcomingChangeEvent.create!(
event_type: :added,
upcoming_change_name: "fresh_setting",
created_at: 1.minute.from_now,
)
expect(serialize_for(admin)[:has_new_upcoming_changes]).to eq(true)
end
it "honors last_visited_upcoming_changes_at when set" do
Discourse.stubs(:site_creation_date).returns(2.years.ago)
admin = Fabricate(:admin)
admin.custom_fields["last_visited_upcoming_changes_at"] = 1.hour.ago.iso8601
admin.save_custom_fields
UpcomingChangeEvent.create!(
event_type: :added,
upcoming_change_name: "before_visit",
created_at: 2.hours.ago,
)
expect(serialize_for(admin)[:has_new_upcoming_changes]).to eq(false)
UpcomingChangeEvent.create!(
event_type: :added,
upcoming_change_name: "after_visit",
created_at: 1.minute.ago,
)
expect(serialize_for(admin)[:has_new_upcoming_changes]).to eq(true)
end
end
end