FEATURE: Hide user status when user is hiding public profile and presence (#24300)

Users can hide their public profile and presence information by checking 
“Hide my public profile and presence features” on the 
`u/{username}/preferences/interface` page. In that case, we also don't 
want to return user status from the server.

This work has been started in https://github.com/discourse/discourse/pull/23946. 
The current PR fixes all the remaining places in Core.

Note that the actual fix is quite simple – a5802f484d. 
But we had a fair amount of duplication in the code responsible for 
the user status serialization, so I had to dry that up first. The refactoring 
as well as adding some additional tests is the main part of this PR.
This commit is contained in:
Andrei Prigorshnev 2024-02-26 13:40:48 +00:00 committed by GitHub
parent 41790f7739
commit b3a1199493
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 480 additions and 172 deletions

View File

@ -1237,7 +1237,7 @@ class UsersController < ApplicationController
if usernames.blank? if usernames.blank?
UserSearch.new(term, options).search UserSearch.new(term, options).search
else else
User.where(username_lower: usernames).limit(limit) User.where(username_lower: usernames).includes(:user_option).limit(limit)
end end
to_render = serialize_found_users(results) to_render = serialize_found_users(results)
@ -2238,9 +2238,12 @@ class UsersController < ApplicationController
end end
def serialize_found_users(users) def serialize_found_users(users)
each_serializer = serializer =
SiteSetting.enable_user_status? ? FoundUserWithStatusSerializer : FoundUserSerializer ActiveModel::ArraySerializer.new(
users,
{ users: ActiveModel::ArraySerializer.new(users, each_serializer: each_serializer).as_json } each_serializer: FoundUserSerializer,
include_status: true,
)
{ users: serializer.as_json }
end end
end end

View File

@ -208,6 +208,7 @@ class UserSearch
) x on uid = users.id", ) x on uid = users.id",
).order("rn") ).order("rn")
results = results.includes(:user_option)
results = results.includes(:user_status) if SiteSetting.enable_user_status results = results.includes(:user_status) if SiteSetting.enable_user_status
results results

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class BasicUserSerializer < ApplicationSerializer class BasicUserSerializer < ApplicationSerializer
include UserStatusMixin
attributes :id, :username, :name, :avatar_template attributes :id, :username, :name, :avatar_template
def name def name

View File

@ -1,13 +0,0 @@
# frozen_string_literal: true
class BasicUserWithStatusSerializer < BasicUserSerializer
attributes :status
def include_status?
SiteSetting.enable_user_status && user.has_status?
end
def status
UserStatusSerializer.new(user.user_status, root: false)
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
module UserStatusMixin
def self.included(klass)
klass.attributes :status
end
def include_status?
@options[:include_status] && SiteSetting.enable_user_status &&
!object.user_option&.hide_profile_and_presence && object.has_status?
end
def status
UserStatusSerializer.new(object.user_status, root: false).as_json
end
end

View File

@ -3,6 +3,7 @@
class CurrentUserSerializer < BasicUserSerializer class CurrentUserSerializer < BasicUserSerializer
include UserTagNotificationsMixin include UserTagNotificationsMixin
include UserSidebarMixin include UserSidebarMixin
include UserStatusMixin
attributes :name, attributes :name,
:unread_notifications, :unread_notifications,
@ -65,7 +66,6 @@ class CurrentUserSerializer < BasicUserSerializer
:can_review, :can_review,
:draft_count, :draft_count,
:pending_posts_count, :pending_posts_count,
:status,
:grouped_unread_notifications, :grouped_unread_notifications,
:display_sidebar_tags, :display_sidebar_tags,
:sidebar_tags, :sidebar_tags,
@ -82,6 +82,11 @@ class CurrentUserSerializer < BasicUserSerializer
has_one :user_option, embed: :object, serializer: CurrentUserOptionSerializer has_one :user_option, embed: :object, serializer: CurrentUserOptionSerializer
def initialize(object, options = {})
super
options[:include_status] = true
end
def sidebar_sections def sidebar_sections
SidebarSection SidebarSection
.public_sections .public_sections
@ -304,14 +309,6 @@ class CurrentUserSerializer < BasicUserSerializer
Draft.has_topic_draft(object) Draft.has_topic_draft(object)
end end
def include_status?
SiteSetting.enable_user_status && object.has_status?
end
def status
UserStatusSerializer.new(object.user_status, root: false)
end
def unseen_reviewable_count def unseen_reviewable_count
Reviewable.unseen_reviewable_count(object) Reviewable.unseen_reviewable_count(object)
end end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class FoundUserSerializer < ApplicationSerializer class FoundUserSerializer < ApplicationSerializer
include UserStatusMixin
attributes :id, :username, :name, :avatar_template attributes :id, :username, :name, :avatar_template
def include_name? def include_name?

View File

@ -1,13 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class FoundUserWithStatusSerializer < FoundUserSerializer class FoundUserWithStatusSerializer < FoundUserSerializer
attributes :status include UserStatusMixin
def include_status? def initialize(object, options = {})
SiteSetting.enable_user_status && object.has_status? super
end options[:include_status] = true
def status
UserStatusSerializer.new(object.user_status, root: false)
end end
end end

View File

@ -2,8 +2,14 @@
class GroupUserSerializer < BasicUserSerializer class GroupUserSerializer < BasicUserSerializer
include UserPrimaryGroupMixin include UserPrimaryGroupMixin
include UserStatusMixin
attributes :name, :title, :last_posted_at, :last_seen_at, :added_at, :timezone, :status attributes :name, :title, :last_posted_at, :last_seen_at, :added_at, :timezone
def initialize(object, options = {})
super
options[:include_status] = true
end
def timezone def timezone
user.user_option.timezone user.user_option.timezone
@ -12,12 +18,4 @@ class GroupUserSerializer < BasicUserSerializer
def include_added_at? def include_added_at?
object.respond_to? :added_at object.respond_to? :added_at
end end
def include_status?
SiteSetting.enable_user_status && user.has_status?
end
def status
UserStatusSerializer.new(user.user_status, root: false)
end
end end

View File

@ -3,7 +3,12 @@
class GroupUserWithCustomFieldsSerializer < UserWithCustomFieldsSerializer class GroupUserWithCustomFieldsSerializer < UserWithCustomFieldsSerializer
include UserPrimaryGroupMixin include UserPrimaryGroupMixin
attributes :name, :title, :last_posted_at, :last_seen_at, :added_at, :timezone, :status attributes :name, :title, :last_posted_at, :last_seen_at, :added_at, :timezone
def initialize(object, options = {})
super
options[:include_status] = true
end
def timezone def timezone
user.user_option.timezone user.user_option.timezone
@ -12,12 +17,4 @@ class GroupUserWithCustomFieldsSerializer < UserWithCustomFieldsSerializer
def include_added_at? def include_added_at?
object.respond_to? :added_at object.respond_to? :added_at
end end
def include_status?
SiteSetting.enable_user_status && user.has_status?
end
def status
UserStatusSerializer.new(user.user_status, root: false)
end
end end

View File

@ -581,12 +581,12 @@ class PostSerializer < BasicPostSerializer
if @topic_view && (mentioned_users = @topic_view.mentioned_users[object.id]) if @topic_view && (mentioned_users = @topic_view.mentioned_users[object.id])
mentioned_users mentioned_users
else else
query = User query = User.includes(:user_option)
query = query.includes(:user_status) if SiteSetting.enable_user_status query = query.includes(:user_status) if SiteSetting.enable_user_status
query = query.where(username: object.mentions) query = query.where(username: object.mentions)
end end
users.map { |user| BasicUserWithStatusSerializer.new(user, root: false) } users.map { |user| BasicUserSerializer.new(user, root: false, include_status: true).as_json }
end end
def include_mentioned_users? def include_mentioned_users?

View File

@ -1,8 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
class UserCardSerializer < BasicUserSerializer class UserCardSerializer < BasicUserSerializer
include UserStatusMixin
attr_accessor :topic_post_count attr_accessor :topic_post_count
def initialize(object, options = {})
super
options[:include_status] = true
end
def self.staff_attributes(*attrs) def self.staff_attributes(*attrs)
attributes(*attrs) attributes(*attrs)
attrs.each do |attr| attrs.each do |attr|
@ -70,8 +77,7 @@ class UserCardSerializer < BasicUserSerializer
:flair_color, :flair_color,
:featured_topic, :featured_topic,
:timezone, :timezone,
:pending_posts_count, :pending_posts_count
:status
untrusted_attributes :bio_excerpt, :website, :website_name, :location, :card_background_upload_url untrusted_attributes :bio_excerpt, :website, :website_name, :location, :card_background_upload_url
@ -223,14 +229,6 @@ class UserCardSerializer < BasicUserSerializer
object.card_background_upload&.url object.card_background_upload&.url
end end
def include_status?
SiteSetting.enable_user_status && user.has_status?
end
def status
UserStatusSerializer.new(user.user_status, root: false)
end
private private
def custom_field_keys def custom_field_keys

View File

@ -2,7 +2,8 @@
# A basic user serializer, with custom fields # A basic user serializer, with custom fields
class UserWithCustomFieldsSerializer < BasicUserSerializer class UserWithCustomFieldsSerializer < BasicUserSerializer
attributes :custom_fields include UserStatusMixin
attribute :custom_fields
def custom_fields def custom_fields
fields = custom_field_keys fields = custom_field_keys

View File

@ -336,7 +336,7 @@ class PresenceChannel
else else
message["leaving_user_ids"] = leaving_user_ids if leaving_user_ids.present? message["leaving_user_ids"] = leaving_user_ids if leaving_user_ids.present?
if entering_user_ids.present? if entering_user_ids.present?
users = User.where(id: entering_user_ids) users = User.where(id: entering_user_ids).includes(:user_option)
message["entering_users"] = ActiveModel::ArraySerializer.new( message["entering_users"] = ActiveModel::ArraySerializer.new(
users, users,
each_serializer: BasicUserSerializer, each_serializer: BasicUserSerializer,

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module Chat module Chat
class ChatableUserSerializer < ::Chat::UserWithCustomFieldsAndStatusSerializer class ChatableUserSerializer < UserWithCustomFieldsSerializer
attributes :can_chat, :has_chat_enabled attributes :can_chat, :has_chat_enabled
def can_chat def can_chat

View File

@ -12,7 +12,13 @@ module Chat
.map do |user| .map do |user|
{ {
identifier: "u-#{user.id}", identifier: "u-#{user.id}",
model: ::Chat::ChatableUserSerializer.new(user, scope: scope, root: false), model:
::Chat::ChatableUserSerializer.new(
user,
scope: scope,
root: false,
include_status: true,
),
type: "user", type: "user",
} }
end end

View File

@ -2,15 +2,21 @@
module Chat module Chat
class DirectMessageSerializer < ApplicationSerializer class DirectMessageSerializer < ApplicationSerializer
attribute :group attributes :group, :users
has_many :users, serializer: ::Chat::ChatableUserSerializer, embed: :objects
def users def users
users = object.direct_message_users.map(&:user).map { |u| u || Chat::NullUser.new } users = object.direct_message_users.map(&:user).map { |u| u || Chat::NullUser.new }
users = users - [scope.user] if users.count > 1
return users - [scope.user] if users.count > 1 serializer =
users ActiveModel::ArraySerializer.new(
users,
each_serializer: ::Chat::ChatableUserSerializer,
scope: scope,
include_status: true,
)
serializer.as_json
end end
end end
end end

View File

@ -18,6 +18,7 @@ module Chat
*( *(
BASIC_ATTRIBUTES + BASIC_ATTRIBUTES +
%i[ %i[
user
mentioned_users mentioned_users
reactions reactions
bookmark bookmark
@ -30,7 +31,6 @@ module Chat
), ),
) )
has_one :user, serializer: Chat::MessageUserSerializer, embed: :objects
has_one :chat_webhook_event, serializer: Chat::WebhookEventSerializer, embed: :objects has_one :chat_webhook_event, serializer: Chat::WebhookEventSerializer, embed: :objects
has_one :in_reply_to, serializer: Chat::InReplyToSerializer, embed: :objects has_one :in_reply_to, serializer: Chat::InReplyToSerializer, embed: :objects
has_many :uploads, serializer: ::UploadSerializer, embed: :objects has_many :uploads, serializer: ::UploadSerializer, embed: :objects
@ -38,11 +38,12 @@ module Chat
def mentioned_users def mentioned_users
object object
.user_mentions .user_mentions
.includes(user: :user_option)
.limit(SiteSetting.max_mentions_per_chat_message) .limit(SiteSetting.max_mentions_per_chat_message)
.map(&:user) .map(&:user)
.compact .compact
.sort_by(&:id) .sort_by(&:id)
.map { |user| BasicUserWithStatusSerializer.new(user, root: false) } .map { |user| BasicUserSerializer.new(user, root: false, include_status: true) }
.as_json .as_json
end end
@ -51,7 +52,8 @@ module Chat
end end
def user def user
object.user || Chat::NullUser.new user = object.user || Chat::NullUser.new
MessageUserSerializer.new(user, root: false, include_status: true).as_json
end end
def excerpt def excerpt
@ -61,6 +63,7 @@ module Chat
def reactions def reactions
object object
.reactions .reactions
.includes(user: :user_option)
.group_by(&:emoji) .group_by(&:emoji)
.map do |emoji, reactions| .map do |emoji, reactions|
next unless Emoji.exists?(emoji) next unless Emoji.exists?(emoji)
@ -169,7 +172,7 @@ module Chat
if sym == :notify_user && if sym == :notify_user &&
( (
scope.current_user == user || user.bot? || scope.current_user == user || object.user.bot? ||
!scope.current_user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map) !scope.current_user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map)
) )
next next

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module Chat module Chat
class MessageUserSerializer < BasicUserWithStatusSerializer class MessageUserSerializer < BasicUserSerializer
attributes :moderator?, :admin?, :staff?, :moderator?, :new_user?, :primary_group_name attributes :moderator?, :admin?, :staff?, :moderator?, :new_user?, :primary_group_name
def moderator? def moderator?

View File

@ -9,7 +9,8 @@ module Chat
:excerpt, :excerpt,
:chat_channel_id, :chat_channel_id,
:deleted_at, :deleted_at,
:mentioned_users :mentioned_users,
:user
def excerpt def excerpt
object.censored_excerpt object.censored_excerpt
@ -18,14 +19,17 @@ module Chat
def mentioned_users def mentioned_users
object object
.user_mentions .user_mentions
.includes(user: :user_option)
.limit(SiteSetting.max_mentions_per_chat_message) .limit(SiteSetting.max_mentions_per_chat_message)
.map(&:user) .map(&:user)
.compact .compact
.sort_by(&:id) .sort_by(&:id)
.map { |user| BasicUserWithStatusSerializer.new(user, root: false) } .map { |user| BasicUserSerializer.new(user, root: false, include_status: true) }
.as_json .as_json
end end
has_one :user, serializer: BasicUserWithStatusSerializer, embed: :objects def user
BasicUserSerializer.new(object.user, root: false, include_status: true).as_json
end
end end
end end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
module Chat
class UserWithCustomFieldsAndStatusSerializer < ::UserWithCustomFieldsSerializer
attributes :status
def include_status?
predicate = SiteSetting.enable_user_status && user.has_status?
if user.association(:user_option).loaded?
predicate = predicate && !user.user_option.hide_profile_and_presence
end
predicate
end
def status
::UserStatusSerializer.new(user.user_status, root: false)
end
end
end

View File

@ -128,26 +128,24 @@ RSpec.describe Chat::Api::ChannelThreadsController do
get "/chat/api/channels/#{public_channel.id}/threads" get "/chat/api/channels/#{public_channel.id}/threads"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(
mentioned_users =
response.parsed_body["threads"] response.parsed_body["threads"]
.find { |thread| thread["id"] == thread_1.id } .find { |thread| thread["id"] == thread_1.id }
.dig("original_message", "mentioned_users"), .dig("original_message", "mentioned_users")
).to eq( expect(mentioned_users.count).to be(2)
[ expect(mentioned_users[0]).to include(
{ "avatar_template" => User.system_avatar_template(current_user.username),
"avatar_template" => User.system_avatar_template(current_user.username), "id" => current_user.id,
"id" => current_user.id, "name" => current_user.name,
"name" => current_user.name, "username" => current_user.username,
"username" => current_user.username, )
}, second_user = thread_2.original_message_user
{ expect(mentioned_users[1]).to include(
"avatar_template" => "avatar_template" => User.system_avatar_template(second_user.username),
User.system_avatar_template(thread_2.original_message_user.username), "id" => second_user.id,
"id" => thread_2.original_message_user.id, "name" => second_user.name,
"name" => thread_2.original_message_user.name, "username" => second_user.username,
"username" => thread_2.original_message_user.username,
},
],
) )
end end

View File

@ -62,6 +62,21 @@ describe Chat::MessageSerializer do
expect(serializer.as_json[:user][:username]).to eq(I18n.t("chat.deleted_chat_username")) expect(serializer.as_json[:user][:username]).to eq(I18n.t("chat.deleted_chat_username"))
end end
end end
context "with user status" do
it "adds status to user if status is enabled" do
message_1.user.set_status!("test", "heart")
SiteSetting.enable_user_status = true
json = serializer.as_json
expect(json[:user][:status]).to be_present
end
it "does not add status to user if status is disabled" do
SiteSetting.enable_user_status = false
json = serializer.as_json
expect(json[:user][:status]).to be_nil
end
end
end end
describe "#deleted_at" do describe "#deleted_at" do
@ -233,6 +248,41 @@ describe Chat::MessageSerializer do
expect { serializer.as_json }.not_to raise_error expect { serializer.as_json }.not_to raise_error
expect(serializer.as_json[:mentioned_users]).to be_empty expect(serializer.as_json[:mentioned_users]).to be_empty
end end
context "with user status" do
fab!(:user_status) { Fabricate(:user_status) }
fab!(:mentioned_user) { Fabricate(:user, user_status: user_status) }
fab!(:message) do
Fabricate(
:chat_message,
message:
"there should be a mention here, but since we're fabricating objects it doesn't matter",
)
end
fab!(:chat_mention) do
Fabricate(:user_chat_mention, chat_message: message, user: mentioned_user)
end
it "adds status to mentioned users when status is enabled" do
SiteSetting.enable_user_status = true
serializer = described_class.new(message, scope: guardian, root: nil)
json = serializer.as_json
expect(json[:mentioned_users][0][:status]).not_to be_nil
expect(json[:mentioned_users][0][:status][:description]).to eq(user_status.description)
expect(json[:mentioned_users][0][:status][:emoji]).to eq(user_status.emoji)
end
it "does not add status to mentioned users when status is enabled" do
SiteSetting.enable_user_status = false
serializer = described_class.new(message, scope: guardian, root: nil)
json = serializer.as_json
expect(json[:mentioned_users][0][:status]).to be_nil
end
end
end end
describe "threading data" do describe "threading data" do

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
RSpec.describe Chat::ChatableUserSerializer do
fab!(:user) { Fabricate(:user) }
subject(:serializer) { described_class.new(user, scope: Guardian.new(user), root: false) }
it "serializes a user" do
json = serializer.as_json
expect(json).to eq(
{
id: user.id,
username: user.username,
name: user.name,
avatar_template: user.avatar_template,
custom_fields: {
},
can_chat: false,
has_chat_enabled: false,
},
)
end
end

View File

@ -10,8 +10,9 @@ describe Chat::DirectMessageSerializer do
direct_message = Fabricate.build(:direct_message, users: [me, you]) direct_message = Fabricate.build(:direct_message, users: [me, you])
serializer = described_class.new(direct_message, scope: Guardian.new(me), root: false) serializer = described_class.new(direct_message, scope: Guardian.new(me), root: false)
json = serializer.as_json
expect(serializer.users).to eq([you]) expect(json[:users].map { |u| u[:username] }).to eq([you.username])
end end
it "returns you both if there are three of us" do it "returns you both if there are three of us" do
@ -21,8 +22,11 @@ describe Chat::DirectMessageSerializer do
direct_message = Fabricate.build(:direct_message, users: [me, you, other_you]) direct_message = Fabricate.build(:direct_message, users: [me, you, other_you])
serializer = described_class.new(direct_message, scope: Guardian.new(me), root: false) serializer = described_class.new(direct_message, scope: Guardian.new(me), root: false)
json = serializer.as_json
expect(serializer.users).to match_array([you, other_you]) expect(json[:users].map { |u| u[:username] }).to match_array(
[you.username, other_you.username],
)
end end
it "returns me if there is only me" do it "returns me if there is only me" do
@ -30,8 +34,9 @@ describe Chat::DirectMessageSerializer do
direct_message = Fabricate.build(:direct_message, users: [me]) direct_message = Fabricate.build(:direct_message, users: [me])
serializer = described_class.new(direct_message, scope: Guardian.new(me), root: false) serializer = described_class.new(direct_message, scope: Guardian.new(me), root: false)
json = serializer.as_json
expect(serializer.users).to eq([me]) expect(json[:users].map { |u| u[:username] }).to eq([me.username])
end end
context "when a user is destroyed" do context "when a user is destroyed" do
@ -43,9 +48,10 @@ describe Chat::DirectMessageSerializer do
you.destroy! you.destroy!
serializer = serializer =
described_class.new(direct_message.reload, scope: Guardian.new(me), root: false).as_json described_class.new(direct_message.reload, scope: Guardian.new(me), root: false)
json = serializer.as_json
expect(serializer[:users][0][:username]).to eq(I18n.t("chat.deleted_chat_username")) expect(json[:users][0][:username]).to eq(I18n.t("chat.deleted_chat_username"))
end end
end end
end end

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
require "rails_helper"
RSpec.describe Chat::ThreadOriginalMessageSerializer do
describe "#user" do
fab!(:user_status) { Fabricate(:user_status) }
fab!(:user) { Fabricate(:user, user_status: user_status) }
fab!(:message) { Fabricate(:chat_message, user: user) }
subject(:serializer) { described_class.new(message, root: nil) }
it "adds status to user if status is enabled" do
SiteSetting.enable_user_status = true
json = serializer.as_json
expect(json[:user][:status]).to be_present
expect(json[:user][:status][:description]).to eq(user_status.description)
expect(json[:user][:status][:emoji]).to eq(user_status.emoji)
end
it "does not add status user if status is disabled" do
SiteSetting.enable_user_status = false
json = serializer.as_json
expect(json[:user][:status]).to be_nil
end
end
context "with mentions" do
fab!(:user_status) { Fabricate(:user_status) }
fab!(:mentioned_user) { Fabricate(:user, user_status: user_status) }
fab!(:message) do
Fabricate(
:chat_message,
message:
"there should be a mention here, but since we're fabricating objects it doesn't matter",
)
end
fab!(:chat_mention) do
Fabricate(:user_chat_mention, chat_message: message, user: mentioned_user)
end
subject(:serializer) { described_class.new(message, root: nil) }
it "adds status to mentioned users if status is enabled" do
SiteSetting.enable_user_status = true
json = serializer.as_json
expect(json[:mentioned_users][0][:status]).to be_present
expect(json[:mentioned_users][0][:status][:description]).to eq(user_status.description)
expect(json[:mentioned_users][0][:status][:emoji]).to eq(user_status.emoji)
end
it "does not add status to mentioned users if status is disabled" do
SiteSetting.enable_user_status = false
json = serializer.as_json
expect(json[:mentioned_users][0][:status]).to be_nil
end
end
end

View File

@ -309,7 +309,7 @@ RSpec.describe Chat::UpdateMessage do
expect(mentioned_user["id"]).to eq(user2.id) expect(mentioned_user["id"]).to eq(user2.id)
expect(mentioned_user["username"]).to eq(user2.username) expect(mentioned_user["username"]).to eq(user2.username)
expect(mentioned_user["status"]).to be_present expect(mentioned_user["status"]).to be_present
expect(mentioned_user["status"].slice(:description, :emoji)).to eq(status) expect(mentioned_user["status"].symbolize_keys.slice(:description, :emoji)).to eq(status)
end end
it "doesn't add mentioned user's status to the message bus message when status is disabled" do it "doesn't add mentioned user's status to the message bus message when status is disabled" do

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe BasicUserSerializer do RSpec.describe BasicUserSerializer do
fab!(:user) { Fabricate(:user) }
let(:serializer) { BasicUserSerializer.new(user, scope: Guardian.new(user), root: false) }
describe "#as_json" do describe "#as_json" do
let(:user) { Fabricate.build(:user) }
let(:serializer) { BasicUserSerializer.new(user, scope: Guardian.new(user), root: false) }
let(:json) { serializer.as_json } let(:json) { serializer.as_json }
it "returns the username" do it "returns the username" do
@ -27,4 +28,59 @@ RSpec.describe BasicUserSerializer do
expect(json[:name]).to eq(nil) expect(json[:name]).to eq(nil)
end end
end end
describe "#status" do
fab!(:user_status) { Fabricate(:user_status) }
before { user.user_status = user_status }
describe "when status is enabled in settings" do
before { SiteSetting.enable_user_status = true }
it "doesn't add status by default" do
serializer = BasicUserSerializer.new(user, root: false)
json = serializer.as_json
expect(json.keys).not_to include :status
end
context "when including user status" do
let(:serializer) { BasicUserSerializer.new(user, root: false, include_status: true) }
it "adds status if `include_status: true` has been passed" do
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 expired user status" do
user.user_status.ends_at = 1.minutes.ago
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
user.clear_status!
user.reload
json = serializer.as_json
expect(json.keys).not_to include :status
end
end
end
describe "when status is disabled in settings" do
before { SiteSetting.enable_user_status = false }
it "doesn't add user status" do
serializer = BasicUserSerializer.new(user, root: false, include_status: true)
json = serializer.as_json
expect(json.keys).not_to include :status
end
end
end
end end

View File

@ -1,44 +0,0 @@
# frozen_string_literal: true
RSpec.describe BasicUserWithStatusSerializer do
fab!(:user_status)
fab!(:user) { Fabricate(:user, user_status: user_status) }
let(:serializer) { described_class.new(user, scope: Guardian.new(user), 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 status is disabled in site settings" 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.minutes.ago
serializer = described_class.new(user, scope: Guardian.new(user), 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

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
RSpec.describe UserStatusMixin do
fab!(:user_status) { Fabricate(:user_status) }
fab!(:user) { Fabricate(:user, user_status: user_status) }
class DummySerializer < ApplicationSerializer
include UserStatusMixin
end
context "when user status is disabled" do
before { SiteSetting.enable_user_status = false }
it "doesn't include status" do
serializer = DummySerializer.new(user, root: false, include_status: true)
json = serializer.as_json
expect(json[:status]).to be_nil
end
end
context "when user status is enabled" do
before { SiteSetting.enable_user_status = true }
it "doesn't include status by default" do
serializer = DummySerializer.new(user, root: false)
json = serializer.as_json
expect(json[:status]).to be_nil
end
it "includes status when include_status option is passed" do
serializer = DummySerializer.new(user, root: false, include_status: true)
json = serializer.as_json
expect(json[:status]).to be_present
end
it "doesn't include status if user hid profile and presence" do
user.user_option.hide_profile_and_presence = true
serializer = DummySerializer.new(user, root: false, include_status: true)
json = serializer.as_json
expect(json[:status]).to be_nil
end
end
end

View File

@ -26,4 +26,59 @@ RSpec.describe FoundUserSerializer do
expect(json.keys).not_to include :name expect(json.keys).not_to include :name
end end
end end
describe "#status" do
fab!(:user_status) { Fabricate(:user_status) }
before { user.user_status = user_status }
context "when status is enabled in site settings" do
before { SiteSetting.enable_user_status = true }
it "doesn't add user status by default" do
serializer = FoundUserSerializer.new(user, root: false)
json = serializer.as_json
expect(json.keys).not_to include :status
end
context "when including user status" do
let(:serializer) { FoundUserSerializer.new(user, root: false, include_status: true) }
it "adds user status when enabled" do
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 expired user status" do
user.user_status.ends_at = 1.minutes.ago
serializer = described_class.new(user, scope: Guardian.new(user), 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" do
user.clear_status!
user.reload
json = serializer.as_json
expect(json.keys).not_to include :status
end
end
end
context "when status is disabled in site settings" do
before { SiteSetting.enable_user_status = false }
let(:serializer) { FoundUserSerializer.new(user, root: false, include_status: true) }
it "doesn't add user status" do
json = serializer.as_json
expect(json.keys).not_to include :status
end
end
end
end end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
RSpec.describe GroupUserWithCustomFieldsSerializer do
describe "#status" do
fab!(:user_status)
fab!(:user) { Fabricate(:user, user_status: user_status) }
it "adds user status when enabled in site settings" do
SiteSetting.enable_user_status = true
serializer = described_class.new(user, scope: Guardian.new(user), root: false)
json = serializer.as_json
expect(json[:status]).to be_present
end
end
end

View File

@ -362,6 +362,35 @@ RSpec.describe PostSerializer do
end end
end end
context "with mentions" do
fab!(:user_status)
fab!(:user) { Fabricate(:user) }
fab!(:user1) { Fabricate(:user, user_status: user_status) }
fab!(:post) { Fabricate(:post, user: user, raw: "Hey @#{user1.username}") }
let(:serializer) { described_class.new(post, scope: Guardian.new(user), root: false) }
it "returns mentioned users with user status when user status is enabled" do
SiteSetting.enable_user_status = true
json = serializer.as_json
expect(json[:mentioned_users]).to be_present
expect(json[:mentioned_users].length).to be(1)
expect(json[:mentioned_users][0]).to_not be_nil
expect(json[:mentioned_users][0][:id]).to eq(user1.id)
expect(json[:mentioned_users][0][:username]).to eq(user1.username)
expect(json[:mentioned_users][0][:name]).to eq(user1.name)
expect(json[:mentioned_users][0][:status][:description]).to eq(user_status.description)
expect(json[:mentioned_users][0][:status][:emoji]).to eq(user_status.emoji)
end
it "doesn't return mentioned users when user status is disabled" do
SiteSetting.enable_user_status = false
json = serializer.as_json
expect(json[:mentioned_users]).to be_nil
end
end
describe "#user_status" do describe "#user_status" do
fab!(:user_status) fab!(:user_status)
fab!(:user) { Fabricate(:user, user_status: user_status) } fab!(:user) { Fabricate(:user, user_status: user_status) }

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
RSpec.describe UserWithCustomFieldsSerializer do
describe "#status" do
fab!(:user_status)
fab!(:user) { Fabricate(:user, user_status: user_status) }
it "adds user status when enabled in site settings" do
SiteSetting.enable_user_status = true
serializer =
described_class.new(user, scope: Guardian.new(user), root: false, include_status: true)
json = serializer.as_json
expect(json[:status]).to be_present
end
end
end