discourse/spec/services/post_owner_changer_spec.rb
Alan Guo Xiang Tan 5bd55acf83
FIX: Add DB constraints for post & topic counter cache for UserStat (#15626)
Ensures that `UserStat#post_count` and `UserStat#topic_count` does not
go below 0. When it does like it did now, we tend to have bugs in our
code since we're usually coding with the assumption that the count isn't
negative.

In order to support the constraints, our post and topic fabricators in
tests will now automatically increment the count for the respective
user's `UserStat` as well. We have to do this because our fabricators
bypasss `PostCreator` which holds the responsibility of updating `UserStat#post_count` and
`UserStat#topic_count`.
2022-02-07 11:23:34 +08:00

252 lines
8.6 KiB
Ruby

# frozen_string_literal: true
require "rails_helper"
describe PostOwnerChanger do
describe "change_owner!" do
fab!(:editor) { Fabricate(:admin) }
fab!(:user_a) { Fabricate(:user) }
let(:p1) { create_post(post_number: 1) }
let(:topic) { p1.topic }
let(:p2) { create_post(topic: topic, post_number: 2) }
let(:p3) { create_post }
it "raises an error with a parameter missing" do
expect {
PostOwnerChanger.new(post_ids: [p1.id], topic_id: topic.id, new_owner: nil, acting_user: editor)
}.to raise_error(ArgumentError)
end
it "calls PostRevisor" do
PostRevisor.any_instance.expects(:revise!)
PostOwnerChanger.new(post_ids: [p1.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
end
it "changes the user" do
bumped_at = freeze_time topic.bumped_at
now = Time.zone.now
freeze_time(now - 1.day)
old_user = p1.user
PostActionCreator.like(user_a, p1)
p1.reload
expect(p1.topic.like_count).to eq(1)
freeze_time(now)
PostOwnerChanger.new(post_ids: [p1.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
p1.reload
expect(p1.topic.like_count).to eq(0)
expect(p1.topic.bumped_at).to eq_time(bumped_at)
expect(p1.topic.last_post_user_id).to eq(user_a.id)
expect(old_user).not_to eq(p1.user)
expect(p1.user).to eq(user_a)
end
it "changes multiple posts" do
PostOwnerChanger.new(post_ids: [p1.id, p2.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
p1.reload; p2.reload
expect(p1.user).not_to eq(nil)
expect(p1.user).to eq(user_a)
expect(p1.user).to eq(p2.user)
end
it "ignores posts in other topics" do
PostOwnerChanger.new(post_ids: [p1.id, p3.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
p1.reload; p3.reload
expect(p1.user).to eq(user_a)
expect(p3.topic_id).not_to eq(p1.topic_id)
expect(p2.user).not_to eq(user_a)
end
it "skips creating new post revision if skip_revision is true" do
PostOwnerChanger.new(post_ids: [p1.id, p2.id], topic_id: topic.id, new_owner: user_a, acting_user: editor, skip_revision: true).change_owner!
p1.reload; p2.reload
expect(p1.revisions.size).to eq(0)
expect(p2.revisions.size).to eq(0)
end
it "changes the user even when the post does not pass validation" do
p1.update_attribute(:raw, "foo")
PostOwnerChanger.new(post_ids: [p1.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
expect(p1.reload.user).to eq(user_a)
end
it "changes the user even when the topic does not pass validation" do
topic.update_column(:title, "short")
PostOwnerChanger.new(post_ids: [p1.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
expect(p1.reload.user).to eq(user_a)
end
it "changes the owner when the post is deleted" do
p4 = Fabricate(:post, topic: topic, reply_to_post_number: p2.post_number)
PostDestroyer.new(editor, p4).destroy
PostOwnerChanger.new(post_ids: [p4.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
expect(p4.reload.user).to eq(user_a)
end
context "sets topic notification level for the new owner" do
let(:p4) { create_post(post_number: 2, topic: topic) }
it "'watching' if the first post gets a new owner" do
PostOwnerChanger.new(post_ids: [p1.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
tu = TopicUser.find_by(user_id: user_a.id, topic_id: topic.id)
expect(tu.notification_level).to eq(3)
end
it "'tracking' if other than the first post gets a new owner" do
PostOwnerChanger.new(post_ids: [p4.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
tu = TopicUser.find_by(user_id: user_a.id, topic_id: topic.id)
expect(tu.notification_level).to eq(2)
end
end
context "integration tests" do
let(:p1user) { p1.user }
let(:p2user) { p2.user }
before do
topic.update!(user_id: p1user.id)
p1user.user_stat.update!(
topic_count: 1,
post_count: 0,
first_post_created_at: p1.created_at,
)
p2user.user_stat.update!(
topic_count: 0,
post_count: 1,
first_post_created_at: p2.created_at,
)
UserAction.create!(action_type: UserAction::NEW_TOPIC, user_id: p1user.id, acting_user_id: p1user.id,
target_post_id: -1, target_topic_id: p1.topic_id, created_at: p1.created_at)
UserAction.create!(action_type: UserAction::REPLY, user_id: p2user.id, acting_user_id: p2user.id,
target_post_id: p2.id, target_topic_id: p2.topic_id, created_at: p2.created_at)
UserActionManager.enable
end
subject(:change_owners) do
PostOwnerChanger.new(
post_ids: [p1.id, p2.id],
topic_id: topic.id,
new_owner: user_a,
acting_user: editor
).change_owner!
end
it "updates users' topic and post counts" do
PostActionCreator.like(p2user, p1)
expect(p1user.user_stat.reload.likes_received).to eq(1)
change_owners
p1user.reload; p2user.reload; user_a.reload
expect(p1user.topic_count).to eq(0)
expect(p1user.post_count).to eq(0)
expect(p2user.topic_count).to eq(0)
expect(p2user.post_count).to eq(0)
expect(user_a.topic_count).to eq(1)
expect(user_a.post_count).to eq(1)
p1_user_stat = p1user.user_stat
expect(p1_user_stat.first_post_created_at).to eq(nil)
expect(p1_user_stat.likes_received).to eq(0)
p2_user_stat = p2user.user_stat
expect(p2_user_stat.first_post_created_at).to eq(nil)
user_a_stat = user_a.user_stat
expect(user_a_stat.first_post_created_at).to be_present
expect(user_a_stat.likes_received).to eq(1)
end
it "handles whispers" do
whisper = PostCreator.new(
editor,
topic_id: p1.topic_id,
reply_to_post_number: 1,
post_type: Post.types[:whisper],
raw: 'this is a whispered reply'
).create
user_stat = editor.user_stat
expect {
PostOwnerChanger.new(
post_ids: [whisper.id],
topic_id: topic.id,
new_owner: Fabricate(:admin),
acting_user: editor
).change_owner!
}.to_not change { user_stat.reload.post_count }
end
context 'private message topic' do
# fab!(:topic) { Fabricate(:private_message_topic) }
let(:pm) do
create_post(
archetype: 'private_message',
target_usernames: [p2user.username]
)
end
let(:pm_poster) { pm.user }
it "should update users' counts" do
PostActionCreator.like(p2user, pm)
expect {
PostOwnerChanger.new(
post_ids: [pm.id],
topic_id: pm.topic_id,
new_owner: user_a,
acting_user: editor
).change_owner!
}.to_not change { pm_poster.user_stat.post_count }
expect(pm_poster.user_stat.likes_received).to eq(0)
user_a_stat = user_a.user_stat
expect(user_a_stat.first_post_created_at).to be_present
expect(user_a_stat.likes_received).to eq(0)
expect(user_a_stat.post_count).to eq(0)
end
end
it "updates UserAction records" do
g = Guardian.new(editor)
expect(UserAction.stats(user_a.id, g)).to eq([])
change_owners
expect(UserAction.stats(p1user.id, g)).to eq([])
expect(UserAction.stats(p2user.id, g)).to eq([])
stats = UserAction.stats(user_a.id, g)
expect(stats.size).to eq(2)
expect(stats[0].action_type).to eq(UserAction::NEW_TOPIC)
expect(stats[0].count).to eq(1)
expect(stats[1].action_type).to eq(UserAction::REPLY)
expect(stats[1].count).to eq(1)
end
it "updates reply_to_user_id" do
p4 = Fabricate(:post, topic: topic, reply_to_post_number: p1.post_number, reply_to_user_id: p1.user_id)
p5 = Fabricate(:post, topic: topic, reply_to_post_number: p2.post_number, reply_to_user_id: p2.user_id)
PostOwnerChanger.new(post_ids: [p1.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner!
p4.reload; p5.reload
expect(p4.reply_to_user_id).to eq(user_a.id)
expect(p5.reply_to_user_id).to eq(p2.user_id)
end
end
end
end