mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
## Summary Fix a bug where users with hidden profiles could still expose their status in post JSON and `/user-status broadcasts` by consistently enforcing `Guardian#can_see_user_status?` before serializing or publishing status updates. ## Source - Patch Triage: https://patch.discourse.org/patch-triage/1287 Co-authored-by: discourse-patch-triage <272280883+discourse-patch-triage[bot]@users.noreply.github.com>
1101 lines
36 KiB
Ruby
Vendored
1101 lines
36 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe PostSerializer do
|
|
fab!(:post)
|
|
|
|
context "with a post with lots of actions" do
|
|
fab!(:actor) { Fabricate(:user, refresh_auto_groups: true) }
|
|
fab!(:admin)
|
|
let(:acted_ids) do
|
|
PostActionType.public_types.values.concat(
|
|
%i[notify_user spam].map { |k| PostActionType.types[k] },
|
|
)
|
|
end
|
|
|
|
def visible_actions_for(user)
|
|
serializer = PostSerializer.new(post, scope: Guardian.new(user), root: false)
|
|
# NOTE this is messy, we should extract all this logic elsewhere
|
|
serializer.post_actions = PostAction.counts_for([post], actor)[post.id] if user.try(:id) ==
|
|
actor.id
|
|
actions = serializer.as_json[:actions_summary]
|
|
lookup = PostActionType.types.invert
|
|
actions.keep_if { |a| (a[:count] || 0) > 0 }.map { |a| lookup[a[:id]] }
|
|
end
|
|
|
|
before do
|
|
acted_ids.each { |id| PostActionCreator.new(actor, post, id).perform }
|
|
post.reload
|
|
end
|
|
|
|
it "displays the correct info" do
|
|
expect(visible_actions_for(actor).sort).to eq(%i[like notify_user spam])
|
|
expect(visible_actions_for(post.user).sort).to eq([:like])
|
|
expect(visible_actions_for(nil).sort).to eq([:like])
|
|
expect(visible_actions_for(admin).sort).to eq(%i[like notify_user spam])
|
|
end
|
|
|
|
it "subtracts likes from ignored users from the like count" do
|
|
ignored_liker = Fabricate(:user, refresh_auto_groups: true)
|
|
regular_liker = Fabricate(:user, refresh_auto_groups: true)
|
|
PostActionCreator.like(ignored_liker, post)
|
|
PostActionCreator.like(regular_liker, post)
|
|
Fabricate(:ignored_user, user: actor, ignored_user: ignored_liker)
|
|
post.reload
|
|
|
|
serializer = PostSerializer.new(post, scope: Guardian.new(actor), root: false)
|
|
like_summary = serializer.actions_summary.find { |a| a[:id] == PostActionType.types[:like] }
|
|
|
|
expect(post.like_count).to eq(3)
|
|
expect(like_summary[:count]).to eq(2)
|
|
end
|
|
|
|
it "does not adjust the like count for anonymous viewers" do
|
|
ignorer = Fabricate(:user, refresh_auto_groups: true)
|
|
ignored_liker = Fabricate(:user, refresh_auto_groups: true)
|
|
PostActionCreator.like(ignored_liker, post)
|
|
Fabricate(:ignored_user, user: ignorer, ignored_user: ignored_liker)
|
|
post.reload
|
|
|
|
serializer = PostSerializer.new(post, scope: Guardian.new, root: false)
|
|
like_summary = serializer.actions_summary.find { |a| a[:id] == PostActionType.types[:like] }
|
|
|
|
expect(like_summary[:count]).to eq(post.like_count)
|
|
end
|
|
|
|
it "batches ignored-like counts across posts in a topic view" do
|
|
other_post = Fabricate(:post, topic: post.topic)
|
|
ignored_liker = Fabricate(:user, refresh_auto_groups: true)
|
|
PostActionCreator.like(ignored_liker, post)
|
|
PostActionCreator.like(ignored_liker, other_post)
|
|
Fabricate(:ignored_user, user: actor, ignored_user: ignored_liker)
|
|
|
|
topic_view = TopicView.new(post.topic, actor)
|
|
queries =
|
|
track_sql_queries do
|
|
topic_view.posts.each do |p|
|
|
serializer = PostSerializer.new(p, scope: Guardian.new(actor), root: false)
|
|
serializer.topic_view = topic_view
|
|
serializer.actions_summary
|
|
end
|
|
end
|
|
|
|
per_post_count_queries =
|
|
queries.count { |sql| sql =~ /COUNT.*FROM "post_actions".*post_id" = \d/m }
|
|
expect(per_post_count_queries).to eq(0)
|
|
end
|
|
|
|
it "uses preloaded ignored-like counts outside a topic view" do
|
|
other_post = Fabricate(:post, topic: post.topic)
|
|
ignored_liker = Fabricate(:user, refresh_auto_groups: true)
|
|
PostActionCreator.like(ignored_liker, post)
|
|
PostActionCreator.like(ignored_liker, other_post)
|
|
Fabricate(:ignored_user, user: actor, ignored_user: ignored_liker)
|
|
|
|
ignored_user_like_counts = PostAction.ignored_user_like_counts_for([post, other_post], actor)
|
|
|
|
queries =
|
|
track_sql_queries do
|
|
[post, other_post].each do |p|
|
|
PostSerializer.new(
|
|
p,
|
|
scope: Guardian.new(actor),
|
|
root: false,
|
|
ignored_user_like_counts: ignored_user_like_counts,
|
|
).actions_summary
|
|
end
|
|
end
|
|
|
|
per_post_count_queries =
|
|
queries.count { |sql| sql =~ /COUNT.*FROM "post_actions".*post_id" = \d/m }
|
|
expect(per_post_count_queries).to eq(0)
|
|
end
|
|
|
|
it "can't flag your own post to notify yourself" do
|
|
serializer = PostSerializer.new(post, scope: Guardian.new(post.user), root: false)
|
|
notify_user_action =
|
|
serializer.actions_summary.find { |a| a[:id] == PostActionType.types[:notify_user] }
|
|
expect(notify_user_action).to be_blank
|
|
end
|
|
|
|
it "should not allow user to flag post and notify non human user" do
|
|
post.update!(user: Discourse.system_user)
|
|
|
|
serializer = PostSerializer.new(post, scope: Guardian.new(actor), root: false)
|
|
|
|
notify_user_action =
|
|
serializer.actions_summary.find { |a| a[:id] == PostActionType.types[:notify_user] }
|
|
|
|
expect(notify_user_action).to eq(nil)
|
|
end
|
|
end
|
|
|
|
context "with a post with reviewable content" do
|
|
let!(:reviewable) do
|
|
PostActionCreator.spam(Fabricate(:user, refresh_auto_groups: true), post).reviewable
|
|
end
|
|
|
|
it "includes the reviewable data" do
|
|
json =
|
|
PostSerializer.new(post, scope: Guardian.new(Fabricate(:moderator)), root: false).as_json
|
|
expect(json[:reviewable_id]).to eq(reviewable.id)
|
|
expect(json[:reviewable_score_count]).to eq(1)
|
|
expect(json[:reviewable_score_pending_count]).to eq(1)
|
|
end
|
|
end
|
|
|
|
context "with a post by a nuked user" do
|
|
subject(:serializer) do
|
|
PostSerializer.new(post, scope: Guardian.new(Fabricate(:admin)), root: false).as_json
|
|
end
|
|
|
|
before { post.update!(user_id: nil, deleted_at: Time.zone.now) }
|
|
|
|
it "serializes correctly" do
|
|
%i[name username display_username avatar_template user_title trust_level].each do |attr|
|
|
expect(serializer[attr]).to be_nil
|
|
end
|
|
%i[moderator staff yours].each { |attr| expect(serializer[attr]).to eq(false) }
|
|
end
|
|
end
|
|
|
|
context "with a post by a suspended user" do
|
|
def serializer
|
|
PostSerializer.new(post, scope: Guardian.new(Fabricate(:admin)), root: false).as_json
|
|
end
|
|
|
|
it "serializes correctly" do
|
|
expect(serializer[:user_suspended]).to be_nil
|
|
|
|
post.user.update!(suspended_till: 1.month.from_now)
|
|
|
|
expect(serializer[:user_suspended]).to eq(true)
|
|
|
|
freeze_time(2.months.from_now)
|
|
|
|
expect(serializer[:user_suspended]).to be_nil
|
|
end
|
|
end
|
|
|
|
describe "#display_username" do
|
|
let(:user) { post.user }
|
|
let(:serializer) { PostSerializer.new(post, scope: Guardian.new, root: false) }
|
|
let(:json) { serializer.as_json }
|
|
|
|
it "returns the display_username it when `enable_names` is on" do
|
|
SiteSetting.enable_names = true
|
|
expect(json[:display_username]).to be_present
|
|
end
|
|
|
|
it "doesn't return the display_username it when `enable_names` is off" do
|
|
SiteSetting.enable_names = false
|
|
expect(json[:display_username]).to be_blank
|
|
end
|
|
end
|
|
|
|
context "with a hidden post with add_raw enabled" do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:raw) { "Raw contents of the post." }
|
|
|
|
context "with a public post" do
|
|
let(:post) { Fabricate(:post, raw: raw, user: user) }
|
|
|
|
it "includes the raw post for everyone" do
|
|
[nil, user, Fabricate(:user), Fabricate(:moderator), Fabricate(:admin)].each do |user|
|
|
expect(serialized_post_for_user(user)[:raw]).to eq(raw)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a hidden post" do
|
|
let(:post) do
|
|
Fabricate(
|
|
:post,
|
|
raw: raw,
|
|
user: user,
|
|
hidden: true,
|
|
hidden_reason_id: Post.hidden_reasons[:flag_threshold_reached],
|
|
)
|
|
end
|
|
|
|
it "includes if the user can see it" do
|
|
expect(serialized_post_for_user(Fabricate(:moderator))[:can_see_hidden_post]).to eq(true)
|
|
expect(serialized_post_for_user(Fabricate(:admin))[:can_see_hidden_post]).to eq(true)
|
|
expect(serialized_post_for_user(user)[:can_see_hidden_post]).to eq(true)
|
|
expect(serialized_post_for_user(Fabricate(:user))[:can_see_hidden_post]).to eq(false)
|
|
end
|
|
|
|
it "shows the raw post only if authorized to see it" do
|
|
expect(serialized_post_for_user(nil)[:raw]).to eq(nil)
|
|
expect(serialized_post_for_user(Fabricate(:user))[:raw]).to eq(nil)
|
|
|
|
expect(serialized_post_for_user(user)[:raw]).to eq(raw)
|
|
expect(serialized_post_for_user(Fabricate(:moderator))[:raw]).to eq(raw)
|
|
expect(serialized_post_for_user(Fabricate(:admin))[:raw]).to eq(raw)
|
|
end
|
|
|
|
it "can view edit history only if authorized" do
|
|
expect(serialized_post_for_user(nil)[:can_view_edit_history]).to eq(false)
|
|
expect(serialized_post_for_user(Fabricate(:user))[:can_view_edit_history]).to eq(false)
|
|
|
|
expect(serialized_post_for_user(user)[:can_view_edit_history]).to eq(true)
|
|
expect(serialized_post_for_user(Fabricate(:moderator))[:can_view_edit_history]).to eq(true)
|
|
expect(serialized_post_for_user(Fabricate(:admin))[:can_view_edit_history]).to eq(true)
|
|
end
|
|
end
|
|
|
|
context "with a hidden revised post" do
|
|
fab!(:post) { Fabricate(:post, raw: "Hello world!", hidden: true) }
|
|
|
|
before do
|
|
SiteSetting.editing_grace_period_max_diff = 1
|
|
|
|
revisor = PostRevisor.new(post)
|
|
revisor.revise!(post.user, raw: "Hello, everyone!")
|
|
end
|
|
|
|
it "will not leak version to users" do
|
|
json = PostSerializer.new(post, scope: Guardian.new(user), root: false).as_json
|
|
expect(json[:version]).to eq(1)
|
|
end
|
|
|
|
it "will show real version to staff" do
|
|
json = PostSerializer.new(post, scope: Guardian.new(Fabricate(:admin)), root: false).as_json
|
|
expect(json[:version]).to eq(2)
|
|
end
|
|
end
|
|
|
|
context "with a public wiki post" do
|
|
let(:post) { Fabricate(:post, raw: raw, user: user, wiki: true) }
|
|
|
|
it "can view edit history" do
|
|
[nil, user, Fabricate(:user), Fabricate(:moderator), Fabricate(:admin)].each do |user|
|
|
expect(serialized_post_for_user(user)[:can_view_edit_history]).to eq(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a hidden wiki post" do
|
|
let(:post) do
|
|
Fabricate(
|
|
:post,
|
|
raw: raw,
|
|
user: user,
|
|
wiki: true,
|
|
hidden: true,
|
|
hidden_reason_id: Post.hidden_reasons[:flag_threshold_reached],
|
|
)
|
|
end
|
|
|
|
it "can view edit history only if authorized" do
|
|
expect(serialized_post_for_user(nil)[:can_view_edit_history]).to eq(false)
|
|
expect(serialized_post_for_user(Fabricate(:user))[:can_view_edit_history]).to eq(false)
|
|
expect(serialized_post_for_user(user)[:can_view_edit_history]).to eq(true)
|
|
expect(serialized_post_for_user(Fabricate(:moderator))[:can_view_edit_history]).to eq(true)
|
|
expect(serialized_post_for_user(Fabricate(:admin))[:can_view_edit_history]).to eq(true)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a post with notices" do
|
|
fab!(:user) { Fabricate(:user, trust_level: 1) }
|
|
fab!(:user_tl1) { Fabricate(:user, trust_level: 1) }
|
|
fab!(:user_tl2) { Fabricate(:user, trust_level: 2) }
|
|
fab!(:post) { Fabricate(:post, user: user) }
|
|
|
|
def json_for_user(user, serializer_opts = {})
|
|
serializer = PostSerializer.new(post, scope: Guardian.new(user), root: false)
|
|
|
|
if serializer_opts[:notice_created_by_users]
|
|
serializer.notice_created_by_users = serializer_opts[:notice_created_by_users]
|
|
end
|
|
|
|
serializer.as_json(serializer_opts)
|
|
end
|
|
|
|
describe "returning_user notice" do
|
|
before do
|
|
post.custom_fields[Post::NOTICE] = {
|
|
type: Post.notices[:returning_user],
|
|
last_posted_at: 1.day.ago,
|
|
}
|
|
post.save_custom_fields
|
|
end
|
|
|
|
it "is visible for TL2+ users (except poster)" do
|
|
expect(json_for_user(nil)[:notice]).to eq(nil)
|
|
expect(json_for_user(user)[:notice]).to eq(nil)
|
|
|
|
SiteSetting.returning_user_notice_tl = 2
|
|
expect(json_for_user(user_tl1)[:notice]).to eq(nil)
|
|
expect(json_for_user(user_tl2)[:notice][:type]).to eq(Post.notices[:returning_user])
|
|
|
|
SiteSetting.returning_user_notice_tl = 1
|
|
expect(json_for_user(user_tl1)[:notice][:type]).to eq(Post.notices[:returning_user])
|
|
expect(json_for_user(user_tl2)[:notice][:type]).to eq(Post.notices[:returning_user])
|
|
end
|
|
end
|
|
|
|
describe "custom notice" do
|
|
fab!(:moderator)
|
|
|
|
before do
|
|
post.custom_fields[Post::NOTICE] = {
|
|
type: Post.notices[:custom],
|
|
raw: "This is a notice",
|
|
cooked: "<p>This is a notice</p>",
|
|
created_by_user_id: moderator.id,
|
|
}
|
|
post.save_custom_fields
|
|
end
|
|
|
|
it "displays for all trust levels" do
|
|
expect(json_for_user(user)[:notice]).to eq(
|
|
{
|
|
cooked: "<p>This is a notice</p>",
|
|
created_by_user_id: moderator.id,
|
|
raw: "This is a notice",
|
|
type: Post.notices[:custom],
|
|
}.with_indifferent_access,
|
|
)
|
|
expect(json_for_user(user_tl1)[:notice]).to eq(
|
|
{
|
|
cooked: "<p>This is a notice</p>",
|
|
created_by_user_id: moderator.id,
|
|
raw: "This is a notice",
|
|
type: Post.notices[:custom],
|
|
}.with_indifferent_access,
|
|
)
|
|
expect(json_for_user(user_tl2)[:notice]).to eq(
|
|
{
|
|
cooked: "<p>This is a notice</p>",
|
|
created_by_user_id: moderator.id,
|
|
raw: "This is a notice",
|
|
type: Post.notices[:custom],
|
|
}.with_indifferent_access,
|
|
)
|
|
end
|
|
|
|
it "only displays the created_by_user for staff" do
|
|
expect(
|
|
json_for_user(user, notice_created_by_users: [moderator])[:notice_created_by_user],
|
|
).to eq(nil)
|
|
expect(
|
|
json_for_user(user_tl1, notice_created_by_users: [moderator])[:notice_created_by_user],
|
|
).to eq(nil)
|
|
expect(
|
|
json_for_user(user_tl2, notice_created_by_users: [moderator])[:notice_created_by_user],
|
|
).to eq(nil)
|
|
expect(
|
|
json_for_user(moderator, notice_created_by_users: [moderator])[:notice_created_by_user],
|
|
).to eq(
|
|
{
|
|
id: moderator.id,
|
|
username: moderator.username,
|
|
name: moderator.name,
|
|
avatar_template: moderator.avatar_template,
|
|
},
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a post with bookmarks" do
|
|
let(:current_user) { Fabricate(:user) }
|
|
let(:topic_view) { TopicView.new(post.topic, current_user) }
|
|
let(:serialized) do
|
|
s = serialized_post(current_user)
|
|
s.post_actions = PostAction.counts_for([post], current_user)[post.id]
|
|
s.topic_view = topic_view
|
|
s
|
|
end
|
|
|
|
context "when a Bookmark record exists for the user on the post" do
|
|
let!(:bookmark) do
|
|
Fabricate(:bookmark_next_business_day_reminder, user: current_user, bookmarkable: post)
|
|
end
|
|
|
|
context "with bookmarks with reminders" do
|
|
it "returns true" do
|
|
expect(serialized.as_json[:bookmarked]).to eq(true)
|
|
end
|
|
|
|
it "returns the reminder_at for the bookmark" do
|
|
expect(serialized.as_json[:bookmark_reminder_at]).to eq(bookmark.reminder_at.iso8601)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with posts when group moderation is enabled" do
|
|
fab!(:topic)
|
|
fab!(:group_user)
|
|
fab!(:post) { Fabricate(:post, topic: topic) }
|
|
fab!(:category_moderation_group) do
|
|
Fabricate(:category_moderation_group, category: topic.category, group: group_user.group)
|
|
end
|
|
|
|
before { SiteSetting.enable_category_group_moderation = true }
|
|
|
|
it "does nothing for regular users" do
|
|
expect(serialized_post_for_user(nil)[:group_moderator]).to eq(nil)
|
|
end
|
|
|
|
it "returns a group_moderator attribute for category group moderators" do
|
|
post.update!(user: group_user.user)
|
|
expect(serialized_post_for_user(nil)[:group_moderator]).to eq(true)
|
|
end
|
|
end
|
|
|
|
context "with a post with small action" do
|
|
fab!(:post) { Fabricate(:small_action, action_code: "public_topic") }
|
|
|
|
it "returns `action_code` based on `login_required` site setting" do
|
|
expect(serialized_post_for_user(nil)[:action_code]).to eq("public_topic")
|
|
SiteSetting.login_required = true
|
|
expect(serialized_post_for_user(nil)[:action_code]).to eq("open_topic")
|
|
end
|
|
end
|
|
|
|
context "with allow_likes_in_anonymous_mode enabled" do
|
|
fab!(:user)
|
|
fab!(:topic) { Fabricate(:topic, user: user) }
|
|
fab!(:post) { Fabricate(:post, topic: topic, user: topic.user) }
|
|
fab!(:anonymous_user, :anonymous)
|
|
|
|
let(:serializer) { PostSerializer.new(post, scope: Guardian.new(anonymous_user), root: false) }
|
|
let(:post_action) do
|
|
user.id = anonymous_user.id
|
|
post.id = 1
|
|
|
|
a =
|
|
PostAction.new(
|
|
user: anonymous_user,
|
|
post: post,
|
|
post_action_type_id: PostActionType.types[:like],
|
|
)
|
|
a.created_at = 1.minute.ago
|
|
a
|
|
end
|
|
|
|
before do
|
|
SiteSetting.allow_anonymous_mode = true
|
|
SiteSetting.allow_likes_in_anonymous_mode = true
|
|
SiteSetting.post_undo_action_window_mins = 10
|
|
PostSerializer.any_instance.stubs(:post_actions).returns({ 2 => post_action })
|
|
end
|
|
|
|
context "when post_undo_action_window_mins has not passed" do
|
|
before { post_action.created_at = 5.minutes.ago }
|
|
|
|
it "allows anonymous users to unlike posts" do
|
|
like_actions_summary =
|
|
serializer.actions_summary.find { |a| a[:id] == PostActionType.types[:like] }
|
|
|
|
#When :can_act is present, the JavaScript allows the user to click the unlike button
|
|
expect(like_actions_summary[:can_act]).to eq(true)
|
|
end
|
|
end
|
|
|
|
context "when post_undo_action_window_mins has passed" do
|
|
before { post_action.created_at = 20.minutes.ago }
|
|
|
|
it "disallows anonymous users from unliking posts" do
|
|
like_actions_summary =
|
|
serializer.actions_summary.find { |a| a[:id] == PostActionType.types[:like] }
|
|
|
|
expect(like_actions_summary[:acted]).to eq(true)
|
|
expect(like_actions_summary[:can_act]).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when user has liked a post but like count is 0 and undo window passed" do
|
|
fab!(:user)
|
|
fab!(:poster, :user)
|
|
fab!(:topic) { Fabricate(:topic, user: poster) }
|
|
fab!(:post) { Fabricate(:post, topic:, user: poster, like_count: 0) }
|
|
fab!(:like_action) do
|
|
Fabricate(
|
|
:post_action,
|
|
user:,
|
|
post:,
|
|
post_action_type_id: PostActionType.types[:like],
|
|
created_at: 1.day.ago,
|
|
)
|
|
end
|
|
|
|
before { SiteSetting.post_undo_action_window_mins = 10 }
|
|
|
|
let(:serializer) do
|
|
PostSerializer.new(
|
|
post,
|
|
scope: Guardian.new(user),
|
|
root: false,
|
|
post_actions: {
|
|
PostActionType.types[:like] => like_action,
|
|
},
|
|
)
|
|
end
|
|
|
|
it "includes the like action in actions_summary with acted flag" do
|
|
like_actions_summary =
|
|
serializer.actions_summary.find { |a| a[:id] == PostActionType.types[:like] }
|
|
|
|
expect(like_actions_summary).to be_present
|
|
expect(like_actions_summary[:acted]).to eq(true)
|
|
expect(like_actions_summary[:can_act]).to be_nil
|
|
expect(like_actions_summary[:can_undo]).to be_nil
|
|
end
|
|
end
|
|
|
|
context "with mentions" do
|
|
fab!(:user_status)
|
|
fab!(:user)
|
|
|
|
let(:username) { "joffrey" }
|
|
let(:user1) { Fabricate(:user, user_status:, username:) }
|
|
let(:post) { Fabricate(:post, user: user, raw: "Hey @#{user1.username}") }
|
|
let(:serializer) { described_class.new(post, scope: Guardian.new(user), root: false) }
|
|
|
|
context "when user status is enabled" do
|
|
before { SiteSetting.enable_user_status = true }
|
|
|
|
it "returns mentioned users with user status" do
|
|
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
|
|
|
|
context "when username has a capital letter" do
|
|
let(:username) { "JoJo" }
|
|
|
|
it "returns mentioned users with user status" do
|
|
expect(serializer.as_json[:mentioned_users][0][:username]).to eq(user1.username)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when user status is disabled" do
|
|
before { SiteSetting.enable_user_status = false }
|
|
|
|
it "doesn't return mentioned users" do
|
|
expect(serializer.as_json[:mentioned_users]).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#user_status" do
|
|
fab!(:user_status)
|
|
fab!(:user) { Fabricate(:user, user_status:) }
|
|
fab!(:post) { Fabricate(:post, user:) }
|
|
|
|
def serialize_user_status(scope: Guardian.new(user))
|
|
described_class.new(post, scope:, root: false).as_json[:user_status]
|
|
end
|
|
|
|
context "when user status is disabled" do
|
|
before { SiteSetting.enable_user_status = false }
|
|
|
|
it "doesn't include status" do
|
|
expect(serialize_user_status).to be_nil
|
|
end
|
|
end
|
|
|
|
context "when user status is enabled" do
|
|
before { SiteSetting.enable_user_status = true }
|
|
|
|
it "includes status" do
|
|
expect(serialize_user_status).to be_present
|
|
end
|
|
|
|
it "doesn't include status if user doesn't have it set" do
|
|
user.clear_status!
|
|
user.reload
|
|
expect(serialize_user_status).to be_nil
|
|
end
|
|
|
|
it "doesn't include status for a public topic author with a hidden profile" do
|
|
SiteSetting.allow_users_to_hide_profile = true
|
|
user.user_option.update!(hide_profile: true)
|
|
|
|
json = described_class.new(post, scope: Guardian.new, root: false).as_json
|
|
|
|
expect(json).not_to have_key(:user_status)
|
|
expect(json.to_json).not_to include(user_status.description)
|
|
end
|
|
|
|
it "respects guardian's can_see_user_status?" do
|
|
user.update!(silenced_till: 1.year.from_now)
|
|
scope = Guardian.new(Fabricate(:user))
|
|
expect(serialize_user_status(scope:)).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#badges_granted" do
|
|
fab!(:user)
|
|
fab!(:user2, :user)
|
|
fab!(:post) { Fabricate(:post, user: user) }
|
|
fab!(:post2) { Fabricate(:post, user: user) }
|
|
|
|
# Create twp badges that have all required flags set to true
|
|
fab!(:badge1) do
|
|
Badge.create!(
|
|
name: "SomeBadge",
|
|
badge_type_id: BadgeType::Bronze,
|
|
listable: true,
|
|
show_posts: true,
|
|
show_in_post_header: true,
|
|
multiple_grant: true,
|
|
)
|
|
end
|
|
fab!(:ub1) do
|
|
UserBadge.create!(
|
|
badge_id: badge1.id,
|
|
user: user,
|
|
granted_by: Discourse.system_user,
|
|
granted_at: Time.now,
|
|
post_id: post.id,
|
|
)
|
|
end
|
|
|
|
fab!(:badge2) do
|
|
Badge.create!(
|
|
name: "SomeOtherBadge",
|
|
badge_type_id: BadgeType::Bronze,
|
|
listable: true,
|
|
show_posts: true,
|
|
show_in_post_header: true,
|
|
multiple_grant: true,
|
|
)
|
|
end
|
|
fab!(:ub2) do
|
|
UserBadge.create!(
|
|
badge_id: badge2.id,
|
|
user: user,
|
|
granted_by: Discourse.system_user,
|
|
granted_at: Time.now,
|
|
post_id: post.id,
|
|
)
|
|
end
|
|
|
|
# Create a badge that has the show_posts flag set to false
|
|
fab!(:badge3) do
|
|
Badge.create!(
|
|
name: "YetAnotherBadge",
|
|
badge_type_id: BadgeType::Bronze,
|
|
listable: true,
|
|
show_posts: false,
|
|
show_in_post_header: true,
|
|
)
|
|
end
|
|
fab!(:ub3) do
|
|
UserBadge.create!(
|
|
badge_id: badge3.id,
|
|
user: user,
|
|
granted_by: Discourse.system_user,
|
|
granted_at: Time.now,
|
|
post_id: post.id,
|
|
)
|
|
end
|
|
|
|
# Re-use our first badge, but on a different post
|
|
fab!(:ub4) do
|
|
UserBadge.create!(
|
|
badge_id: badge1.id,
|
|
user: user,
|
|
granted_by: Discourse.system_user,
|
|
granted_at: Time.now,
|
|
post_id: post2.id,
|
|
)
|
|
end
|
|
|
|
# Now re-use our first badge, but on a different user
|
|
fab!(:ub5) do
|
|
UserBadge.create!(
|
|
badge_id: badge1.id,
|
|
user: user2,
|
|
granted_by: Discourse.system_user,
|
|
granted_at: Time.now,
|
|
post_id: post.id,
|
|
)
|
|
end
|
|
|
|
# Create a badge that has the listable flag set to false
|
|
fab!(:badge4) do
|
|
Badge.create!(
|
|
name: "WeirdBadge",
|
|
badge_type_id: BadgeType::Bronze,
|
|
listable: false,
|
|
show_posts: true,
|
|
show_in_post_header: true,
|
|
)
|
|
end
|
|
fab!(:ub6) do
|
|
UserBadge.create!(
|
|
badge_id: badge4.id,
|
|
user: user,
|
|
granted_by: Discourse.system_user,
|
|
granted_at: Time.now,
|
|
post_id: post.id,
|
|
)
|
|
end
|
|
|
|
# Create a badge that has the show_in_post_header flag set to false
|
|
fab!(:badge5) do
|
|
Badge.create!(
|
|
name: "StrangeBadge",
|
|
badge_type_id: BadgeType::Bronze,
|
|
listable: true,
|
|
show_posts: true,
|
|
show_in_post_header: false,
|
|
)
|
|
end
|
|
fab!(:ub7) do
|
|
UserBadge.create!(
|
|
badge_id: badge5.id,
|
|
user: user,
|
|
granted_by: Discourse.system_user,
|
|
granted_at: Time.now,
|
|
post_id: post.id,
|
|
)
|
|
end
|
|
|
|
let(:serializer) { described_class.new(post, scope: Guardian.new(user), root: false) }
|
|
|
|
it "doesn't include badges when `enable_badges` site setting is disabled" do
|
|
SiteSetting.enable_badges = false
|
|
expect(serializer.as_json[:badges_granted]).to eq([])
|
|
end
|
|
|
|
it "doesn't include badges when `show_badges_in_post_header` site setting is disabled" do
|
|
SiteSetting.enable_badges = true
|
|
SiteSetting.show_badges_in_post_header = false
|
|
expect(serializer.as_json[:badges_granted]).to eq([])
|
|
end
|
|
|
|
context "when `enable_badges` and `show_badges_in_post_header` site settings are enabled" do
|
|
before do
|
|
SiteSetting.enable_badges = true
|
|
SiteSetting.show_badges_in_post_header = true
|
|
end
|
|
|
|
it "includes badges that were granted for this user on this post" do
|
|
json = serializer.as_json
|
|
|
|
expect(json[:badges_granted].length).to eq(2)
|
|
expect(json[:badges_granted].map { |b| b[:badges][0][:id] }).to contain_exactly(
|
|
ub1.badge_id,
|
|
ub2.badge_id,
|
|
)
|
|
|
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).to contain_exactly(
|
|
ub1.id,
|
|
ub2.id,
|
|
)
|
|
end
|
|
|
|
it "does not return a user badge that has the show_posts flag set to false" do
|
|
json = serializer.as_json
|
|
|
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub3.id)
|
|
end
|
|
|
|
it "does not return a user badge that was not granted for this post" do
|
|
json = serializer.as_json
|
|
|
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub4.id)
|
|
end
|
|
|
|
it "does not return a user badge that was granted for a different user" do
|
|
json = serializer.as_json
|
|
|
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub5.id)
|
|
end
|
|
|
|
it "does not return a user badge that has the listable flag set to false" do
|
|
json = serializer.as_json
|
|
|
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub6.id)
|
|
end
|
|
|
|
it "does not return a user badge that has the show_in_post_header flag set to false" do
|
|
json = serializer.as_json
|
|
|
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub7.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#raw" do
|
|
fab!(:user)
|
|
let(:serializer) { serialized_post }
|
|
let(:json) { serializer.as_json }
|
|
|
|
it "returns the post's raw" do
|
|
expect(json[:raw]).to eq(post.raw)
|
|
end
|
|
end
|
|
|
|
describe "#locale" do
|
|
let(:serializer) { serialized_post }
|
|
let(:json) { serializer.as_json }
|
|
|
|
it "is included when content_localization_enabled is enabled" do
|
|
SiteSetting.content_localization_enabled = true
|
|
post.update!(locale: "ja")
|
|
|
|
expect(json[:locale]).to eq("ja")
|
|
end
|
|
|
|
it "is excluded when content_localization_enabled is disabled" do
|
|
SiteSetting.content_localization_enabled = false
|
|
post.update!(locale: "ja")
|
|
|
|
expect(json[:locale]).to eq(nil)
|
|
end
|
|
end
|
|
|
|
describe "#is_localized?" do
|
|
let(:serializer) { serialized_post }
|
|
let(:json) { serializer.as_json }
|
|
|
|
it "is excluded when content_localization_enabled is disabled" do
|
|
SiteSetting.content_localization_enabled = false
|
|
|
|
expect(json[:is_localized]).to eq(nil)
|
|
end
|
|
|
|
describe "content localization enabled" do
|
|
before do
|
|
SiteSetting.content_localization_enabled = true
|
|
I18n.locale = "en"
|
|
end
|
|
|
|
it "returns true when the post is localized" do
|
|
post.update!(locale: "ja")
|
|
Fabricate(:post_localization, post:, locale: "en")
|
|
|
|
expect(json[:is_localized]).to eq(true)
|
|
end
|
|
|
|
it "returns false when the post is same language as user" do
|
|
post.update!(locale: "ja")
|
|
I18n.locale = "ja"
|
|
|
|
expect(json[:is_localized]).to eq(false)
|
|
end
|
|
|
|
it "returns false when no localization" do
|
|
post.update!(locale: "ja")
|
|
|
|
expect(json[:is_localized]).to eq(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#language" do
|
|
let(:serializer) { serialized_post }
|
|
let(:json) { serializer.as_json }
|
|
|
|
it "is excluded when content_localization_enabled is disabled or no locale" do
|
|
SiteSetting.content_localization_enabled = false
|
|
post.update!(locale: "ja")
|
|
expect(serializer.as_json[:language]).to eq(nil)
|
|
|
|
SiteSetting.content_localization_enabled = true
|
|
post.update!(locale: nil)
|
|
expect(serializer.as_json[:language]).to eq(nil)
|
|
end
|
|
|
|
it "shows the language of the post based on locale" do
|
|
SiteSetting.content_localization_enabled = true
|
|
post.update!(locale: "ja")
|
|
|
|
expect(json[:language]).to eq("ja")
|
|
end
|
|
|
|
it "defaults to locale if language does not exist" do
|
|
SiteSetting.content_localization_enabled = true
|
|
post.update!(locale: "aa")
|
|
|
|
expect(json[:language]).to eq("aa")
|
|
end
|
|
end
|
|
|
|
describe "#localization_outdated?" do
|
|
let(:serializer) { serialized_post }
|
|
let(:json) { serializer.as_json }
|
|
|
|
it "is excluded when content_localization_enabled is disabled" do
|
|
SiteSetting.content_localization_enabled = false
|
|
expect(json[:localization_outdated]).to eq(nil)
|
|
end
|
|
|
|
it "is true when the post is localized and the localization is outdated" do
|
|
SiteSetting.content_localization_enabled = true
|
|
post.update!(locale: "ja", version: 3)
|
|
Fabricate(:post_localization, post:, locale: "en", post_version: 2)
|
|
|
|
expect(json[:localization_outdated]).to eq(true)
|
|
end
|
|
|
|
it "is false when the post is localized and the localization is not outdated" do
|
|
SiteSetting.content_localization_enabled = true
|
|
post.update!(locale: "ja", version: 10)
|
|
Fabricate(:post_localization, post:, locale: "en", post_version: 10)
|
|
|
|
expect(json[:localization_outdated]).to eq(false)
|
|
end
|
|
end
|
|
|
|
def serialized_post(u = nil)
|
|
s = PostSerializer.new(post, scope: Guardian.new(u), root: false)
|
|
s.add_raw = true
|
|
s
|
|
end
|
|
|
|
def serialized_post_for_user(u)
|
|
s = serialized_post(u)
|
|
s.as_json
|
|
end
|
|
|
|
describe "#can_localize_post" do
|
|
fab!(:author, :user)
|
|
fab!(:author_post) { Fabricate(:post, user: author) }
|
|
fab!(:admin)
|
|
fab!(:group)
|
|
|
|
before do
|
|
SiteSetting.content_localization_enabled = true
|
|
SiteSetting.content_localization_allowed_groups = group.id.to_s
|
|
end
|
|
|
|
it "is included when user can localize content" do
|
|
group.add(admin)
|
|
json = PostSerializer.new(author_post, scope: Guardian.new(admin), root: false).as_json
|
|
expect(json[:can_localize_post]).to eq(true)
|
|
end
|
|
|
|
it "is included when author localization is enabled and user is post author" do
|
|
SiteSetting.content_localization_allow_author_localization = true
|
|
json = PostSerializer.new(author_post, scope: Guardian.new(author), root: false).as_json
|
|
expect(json[:can_localize_post]).to eq(true)
|
|
|
|
SiteSetting.content_localization_allow_author_localization = false
|
|
json = PostSerializer.new(author_post, scope: Guardian.new(author), root: false).as_json
|
|
expect(json[:can_localize_post]).to eq(nil)
|
|
end
|
|
|
|
it "is not included when user cannot localize post" do
|
|
other_user = Fabricate(:user)
|
|
json = PostSerializer.new(author_post, scope: Guardian.new(other_user), root: false).as_json
|
|
expect(json.key?(:can_localize_post)).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "post_localizations_count" do
|
|
fab!(:author, :user)
|
|
fab!(:author_post) { Fabricate(:post, user: author) }
|
|
fab!(:group)
|
|
|
|
before do
|
|
SiteSetting.content_localization_enabled = true
|
|
SiteSetting.content_localization_allowed_groups = group.id.to_s
|
|
Fabricate(:post_localization, post: author_post, locale: "ja")
|
|
end
|
|
|
|
it "is included for users in allowed groups" do
|
|
user = Fabricate(:user)
|
|
group.add(user)
|
|
json = PostSerializer.new(author_post, scope: Guardian.new(user), root: false).as_json
|
|
expect(json[:post_localizations_count]).to eq(1)
|
|
end
|
|
|
|
it "is included for post authors when author localization is enabled" do
|
|
SiteSetting.content_localization_allow_author_localization = true
|
|
json = PostSerializer.new(author_post, scope: Guardian.new(author), root: false).as_json
|
|
expect(json[:post_localizations_count]).to eq(1)
|
|
end
|
|
|
|
it "is not included for users who cannot localize" do
|
|
other_user = Fabricate(:user)
|
|
json = PostSerializer.new(author_post, scope: Guardian.new(other_user), root: false).as_json
|
|
expect(json.key?(:post_localizations_count)).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "#localized_oneboxes" do
|
|
fab!(:reader) { Fabricate(:user, locale: "ja") }
|
|
fab!(:source_topic, :topic)
|
|
fab!(:source_post) do
|
|
Fabricate(:post, topic: source_topic, post_number: 1, locale: "ja", raw: "見てください")
|
|
end
|
|
fab!(:linked_topic) { Fabricate(:topic, title: "Sun Tzu's strategies", locale: "en") }
|
|
fab!(:linked_post) do
|
|
Fabricate(:post, topic: linked_topic, post_number: 1, locale: "en", raw: "Subdue the enemy.")
|
|
end
|
|
|
|
before do
|
|
SiteSetting.content_localization_enabled = true
|
|
Fabricate(:topic_localization, topic: linked_topic, locale: "ja", title: "孫子の兵法")
|
|
Fabricate(:post_localization, post: linked_post, locale: "ja", cooked: "<p>戦わずして勝つ</p>")
|
|
TopicLink.create!(
|
|
topic: source_topic,
|
|
post: source_post,
|
|
user: source_post.user,
|
|
url: linked_post.url,
|
|
domain: Discourse.current_hostname,
|
|
internal: true,
|
|
quote: true,
|
|
reflection: false,
|
|
link_topic_id: linked_topic.id,
|
|
link_post_id: linked_post.id,
|
|
)
|
|
end
|
|
|
|
def json_for(viewer, scope: nil)
|
|
I18n.with_locale(:ja) do
|
|
serializer =
|
|
PostSerializer.new(source_post, scope: scope || Guardian.new(viewer), root: false)
|
|
serializer.topic_view = TopicView.new(source_topic.id, viewer)
|
|
serializer.as_json
|
|
end
|
|
end
|
|
|
|
it "includes the localized title and preview for the reader" do
|
|
entry = json_for(reader)[:localized_oneboxes].first
|
|
expect(entry[:title]).to eq("孫子の兵法")
|
|
expect(entry[:excerpt]).to include("戦わずして勝つ")
|
|
end
|
|
|
|
it "is omitted when the reader chose to see original content" do
|
|
reader.user_option.update!(show_original_content: true)
|
|
expect(json_for(reader).key?(:localized_oneboxes)).to eq(false)
|
|
end
|
|
|
|
it "is omitted for an anonymous reader with the show-original cookie" do
|
|
env = create_request_env.merge("HTTP_COOKIE" => ContentLocalization::SHOW_ORIGINAL_COOKIE)
|
|
anon_scope = Guardian.new(nil, ActionDispatch::Request.new(env))
|
|
|
|
expect(json_for(nil, scope: anon_scope).key?(:localized_oneboxes)).to eq(false)
|
|
end
|
|
|
|
it "is included for an anonymous reader without the show-original cookie" do
|
|
anon_scope = Guardian.new(nil, ActionDispatch::Request.new(create_request_env))
|
|
|
|
expect(json_for(nil, scope: anon_scope)[:localized_oneboxes].first[:title]).to eq("孫子の兵法")
|
|
end
|
|
|
|
it "is omitted when content localization is disabled" do
|
|
SiteSetting.content_localization_enabled = false
|
|
expect(json_for(reader).key?(:localized_oneboxes)).to eq(false)
|
|
end
|
|
end
|
|
end
|