discourse/spec/models/topic_user_spec.rb
Krzysztof Kotlarek 702d0620d7
DEV: Convert min_trust_to_create_topic to groups (#24740)
This change converts the min_trust_to_create_topic site setting to
create_topic_allowed_groups.

See: https://meta.discourse.org/t/283408

- Hides the old setting
- Adds the new site setting
- Add a deprecation warning
- Updates to use the new setting
- Adds a migration to fill in the new setting if the old setting was
changed
- Adds an entry to the site_setting.keywords section
- Updates tests to account for the new change
- After a couple of months, we will remove the min_trust_to_create_topicsetting entirely.

Internal ref: /t/117248
2023-12-13 14:50:13 +11:00

615 lines
20 KiB
Ruby

# frozen_string_literal: true
RSpec.describe TopicUser do
let :watching do
TopicUser.notification_levels[:watching]
end
let :regular do
TopicUser.notification_levels[:regular]
end
let :tracking do
TopicUser.notification_levels[:tracking]
end
describe "#unwatch_categories!" do
it "correctly unwatches categories" do
op_topic = Fabricate(:topic)
another_topic = Fabricate(:topic)
tracked_topic = Fabricate(:topic)
user = op_topic.user
TopicUser.change(user.id, op_topic, notification_level: watching)
TopicUser.change(user.id, another_topic, notification_level: watching)
TopicUser.change(
user.id,
tracked_topic,
notification_level: watching,
total_msecs_viewed: SiteSetting.default_other_auto_track_topics_after_msecs + 1,
)
TopicUser.unwatch_categories!(user, [Fabricate(:category).id, Fabricate(:category).id])
expect(TopicUser.get(another_topic, user).notification_level).to eq(watching)
TopicUser.unwatch_categories!(user, [op_topic.category_id])
expect(TopicUser.get(op_topic, user).notification_level).to eq(watching)
expect(TopicUser.get(another_topic, user).notification_level).to eq(regular)
expect(TopicUser.get(tracked_topic, user).notification_level).to eq(tracking)
end
end
describe "#notification_levels" do
context "when verifying enum sequence" do
before { @notification_levels = TopicUser.notification_levels }
it "'muted' should be at 0 position" do
expect(@notification_levels[:muted]).to eq(0)
end
it "'watching' should be at 3rd position" do
expect(@notification_levels[:watching]).to eq(3)
end
end
end
describe "#notification_reasons" do
context "when verifying enum sequence" do
before { @notification_reasons = TopicUser.notification_reasons }
it "'created_topic' should be at 1st position" do
expect(@notification_reasons[:created_topic]).to eq(1)
end
it "'plugin_changed' should be at 9th position" do
expect(@notification_reasons[:plugin_changed]).to eq(9)
end
end
end
it { is_expected.to belong_to :user }
it { is_expected.to belong_to :topic }
fab!(:user)
let(:topic) do
u = Fabricate(:user, refresh_auto_groups: true)
guardian = Guardian.new(u)
TopicCreator.create(u, guardian, title: "this is my topic title")
end
let(:topic_user) { TopicUser.get(topic, user) }
let(:topic_creator_user) { TopicUser.get(topic, topic.user) }
let(:new_user) do
u = Fabricate(:user)
u.user_option.update_columns(auto_track_topics_after_msecs: 1000)
u
end
let(:topic_new_user) { TopicUser.get(topic, new_user) }
let(:yesterday) { DateTime.now.yesterday }
def ensure_topic_user
TopicUser.change(user, topic, last_emailed_post_number: 1)
end
describe "unpinned" do
it "defaults to blank" do
ensure_topic_user
expect(topic_user.cleared_pinned_at).to be_blank
end
end
describe "notifications" do
it "should trigger the right DiscourseEvent" do
called = false
blk = Proc.new { called = true }
begin
DiscourseEvent.on(:topic_notification_level_changed, &blk)
TopicUser.change(
user.id,
topic.id,
notification_level: TopicUser.notification_levels[:tracking],
)
expect(called).to eq(true)
ensure
DiscourseEvent.off(:topic_notification_level_changed, &blk)
end
end
it "should be set to tracking if auto_track_topics is enabled" do
user.user_option.update_column(:auto_track_topics_after_msecs, 0)
ensure_topic_user
expect(TopicUser.get(topic, user).notification_level).to eq(
TopicUser.notification_levels[:tracking],
)
end
it "should reset regular topics to tracking topics if auto track is changed" do
ensure_topic_user
user.user_option.auto_track_topics_after_msecs = 0
user.user_option.save
expect(topic_user.notification_level).to eq(TopicUser.notification_levels[:tracking])
end
it 'should be set to "regular" notifications, by default on non creators' do
ensure_topic_user
expect(TopicUser.get(topic, user).notification_level).to eq(
TopicUser.notification_levels[:regular],
)
end
it "reason should reset when changed" do
topic.notify_muted!(topic.user)
expect(TopicUser.get(topic, topic.user).notifications_reason_id).to eq(
TopicUser.notification_reasons[:user_changed],
)
end
it "should have the correct reason for a user change when watched" do
topic.notify_watch!(user)
expect(topic_user.notification_level).to eq(TopicUser.notification_levels[:watching])
expect(topic_user.notifications_reason_id).to eq(
TopicUser.notification_reasons[:user_changed],
)
expect(topic_user.notifications_changed_at).not_to eq(nil)
end
it "should have the correct reason for a user change when set to regular" do
topic.notify_regular!(user)
expect(topic_user.notification_level).to eq(TopicUser.notification_levels[:regular])
expect(topic_user.notifications_reason_id).to eq(
TopicUser.notification_reasons[:user_changed],
)
expect(topic_user.notifications_changed_at).not_to eq(nil)
end
it "should have the correct reason for a user change when set to regular" do
topic.notify_muted!(user)
expect(topic_user.notification_level).to eq(TopicUser.notification_levels[:muted])
expect(topic_user.notifications_reason_id).to eq(
TopicUser.notification_reasons[:user_changed],
)
expect(topic_user.notifications_changed_at).not_to eq(nil)
end
it "should watch topics a user created" do
expect(topic_creator_user.notification_level).to eq(TopicUser.notification_levels[:watching])
expect(topic_creator_user.notifications_reason_id).to eq(
TopicUser.notification_reasons[:created_topic],
)
end
end
describe "visited at" do
it "set upon initial visit" do
freeze_time yesterday
TopicUser.track_visit!(topic.id, user.id)
expect(topic_user.first_visited_at.to_i).to eq(yesterday.to_i)
expect(topic_user.last_visited_at.to_i).to eq(yesterday.to_i)
end
it "updates upon repeat visit" do
freeze_time yesterday
TopicUser.track_visit!(topic.id, user.id)
freeze_time Time.zone.now
TopicUser.track_visit!(topic.id, user.id)
# reload is a no go
topic_user = TopicUser.get(topic, user)
expect(topic_user.first_visited_at.to_i).to eq(yesterday.to_i)
expect(topic_user.last_visited_at.to_i).to eq(Time.zone.now.to_i)
end
end
describe "read tracking" do
context "without auto tracking" do
let(:topic_user) { TopicUser.get(topic, user) }
it "should create a new record for a visit" do
freeze_time yesterday
TopicUser.update_last_read(user, topic.id, 1, 1, 0)
expect(topic_user.last_read_post_number).to eq(1)
expect(topic_user.last_visited_at.to_i).to eq(yesterday.to_i)
expect(topic_user.first_visited_at.to_i).to eq(yesterday.to_i)
end
it "should update the record for repeat visit" do
today = Time.zone.now
freeze_time Time.zone.now
TopicUser.update_last_read(user, topic.id, 1, 1, 0)
tomorrow = 1.day.from_now
freeze_time tomorrow
Fabricate(:post, topic: topic, user: user)
channel = TopicTrackingState.unread_channel_key(user.id)
messages =
MessageBus.track_publish(channel) { TopicUser.update_last_read(user, topic.id, 2, 1, 0) }
expect(messages.blank?).to eq(false)
topic_user = TopicUser.get(topic, user)
expect(topic_user.last_read_post_number).to eq(2)
expect(topic_user.last_visited_at.to_i).to eq(today.to_i)
expect(topic_user.first_visited_at.to_i).to eq(today.to_i)
end
end
context "with private messages" do
fab!(:target_user) { Fabricate(:user) }
let(:post) do
create_post(archetype: Archetype.private_message, target_usernames: target_user.username)
end
let(:topic) { post.topic }
it "should ensure recipients and senders are watching" do
expect(TopicUser.get(topic, post.user).notification_level).to eq(
TopicUser.notification_levels[:watching],
)
expect(TopicUser.get(topic, target_user).notification_level).to eq(
TopicUser.notification_levels[:watching],
)
end
it "should ensure invited user is watching once visited" do
another_user = Fabricate(:user)
topic.invite(target_user, another_user.username)
TopicUser.track_visit!(topic.id, another_user.id)
expect(TopicUser.get(topic, another_user).notification_level).to eq(
TopicUser.notification_levels[:watching],
)
another_user = Fabricate(:user)
TopicUser.track_visit!(topic.id, another_user.id)
expect(TopicUser.get(topic, another_user).notification_level).to eq(
TopicUser.notification_levels[:regular],
)
end
it "should publish the right message_bus message" do
TopicUser.update_last_read(user, topic.id, 1, 1, 0)
Fabricate(:post, topic: topic, user: user)
channel = PrivateMessageTopicTrackingState.user_channel(user.id)
messages =
MessageBus.track_publish(channel) { TopicUser.update_last_read(user, topic.id, 2, 1, 0) }
expect(messages.blank?).to eq(false)
end
describe "inviting a group" do
let(:group) do
Fabricate(:group, default_notification_level: NotificationLevels.topic_levels[:tracking])
end
it "should use group's default notification level" do
another_user = Fabricate(:user)
group.add(another_user)
Jobs.run_immediately!
topic.invite_group(target_user, group)
expect(TopicUser.get(topic, another_user).notification_level).to eq(
TopicUser.notification_levels[:tracking],
)
another_user = Fabricate(:user)
topic.invite(target_user, another_user.username)
TopicUser.track_visit!(topic.id, another_user.id)
expect(TopicUser.get(topic, another_user).notification_level).to eq(
TopicUser.notification_levels[:watching],
)
end
end
end
context "with auto tracking" do
let(:post_creator) do
PostCreator.new(new_user, raw: Fabricate.build(:post).raw, topic_id: topic.id)
end
before { TopicUser.update_last_read(new_user, topic.id, 2, 2, 0) }
it "should automatically track topics you reply to" do
post_creator.create
expect(topic_new_user.notification_level).to eq(TopicUser.notification_levels[:tracking])
expect(topic_new_user.notifications_reason_id).to eq(
TopicUser.notification_reasons[:created_post],
)
end
it "should update tracking state when you reply" do
new_user.user_option.update_column(:notification_level_when_replying, 3)
post_creator.create
DB.exec(
"UPDATE topic_users set notification_level=2
WHERE topic_id = :topic_id AND user_id = :user_id",
topic_id: topic_new_user.topic_id,
user_id: topic_new_user.user_id,
)
TopicUser.auto_notification(
topic_new_user.user_id,
topic_new_user.topic_id,
TopicUser.notification_reasons[:created_post],
TopicUser.notification_levels[:watching],
)
tu = TopicUser.find_by(user_id: topic_new_user.user_id, topic_id: topic_new_user.topic_id)
expect(tu.notification_level).to eq(TopicUser.notification_levels[:watching])
end
it "should not update tracking state when you reply" do
new_user.user_option.update_column(:notification_level_when_replying, 3)
post_creator.create
DB.exec(
"UPDATE topic_users set notification_level=3
WHERE topic_id = :topic_id AND user_id = :user_id",
topic_id: topic_new_user.topic_id,
user_id: topic_new_user.user_id,
)
TopicUser.auto_notification(
topic_new_user.user_id,
topic_new_user.topic_id,
TopicUser.notification_reasons[:created_post],
TopicUser.notification_levels[:tracking],
)
tu = TopicUser.find_by(user_id: topic_new_user.user_id, topic_id: topic_new_user.topic_id)
expect(tu.notification_level).to eq(TopicUser.notification_levels[:watching])
end
it "should not update tracking state when state manually set to normal you reply" do
new_user.user_option.update_column(:notification_level_when_replying, 3)
post_creator.create
DB.exec(
"UPDATE topic_users set notification_level=1
WHERE topic_id = :topic_id AND user_id = :user_id",
topic_id: topic_new_user.topic_id,
user_id: topic_new_user.user_id,
)
TopicUser.auto_notification(
topic_new_user.user_id,
topic_new_user.topic_id,
TopicUser.notification_reasons[:created_post],
TopicUser.notification_levels[:tracking],
)
tu = TopicUser.find_by(user_id: topic_new_user.user_id, topic_id: topic_new_user.topic_id)
expect(tu.notification_level).to eq(TopicUser.notification_levels[:regular])
end
it "should not update tracking state when state manually set to muted you reply" do
new_user.user_option.update_column(:notification_level_when_replying, 3)
post_creator.create
DB.exec(
"UPDATE topic_users set notification_level=0
WHERE topic_id = :topic_id AND user_id = :user_id",
topic_id: topic_new_user.topic_id,
user_id: topic_new_user.user_id,
)
TopicUser.auto_notification(
topic_new_user.user_id,
topic_new_user.topic_id,
TopicUser.notification_reasons[:created_post],
TopicUser.notification_levels[:tracking],
)
tu = TopicUser.find_by(user_id: topic_new_user.user_id, topic_id: topic_new_user.topic_id)
expect(tu.notification_level).to eq(TopicUser.notification_levels[:muted])
end
it "should not automatically track topics you reply to and have set state manually" do
post_creator.create
TopicUser.change(
new_user,
topic,
notification_level: TopicUser.notification_levels[:regular],
)
expect(topic_new_user.notification_level).to eq(TopicUser.notification_levels[:regular])
expect(topic_new_user.notifications_reason_id).to eq(
TopicUser.notification_reasons[:user_changed],
)
end
it "should automatically track topics after they are read for long enough" do
expect(topic_new_user.notification_level).to eq(TopicUser.notification_levels[:regular])
TopicUser.update_last_read(
new_user,
topic.id,
2,
2,
SiteSetting.default_other_auto_track_topics_after_msecs + 1,
)
expect(TopicUser.get(topic, new_user).notification_level).to eq(
TopicUser.notification_levels[:tracking],
)
end
it "should not automatically track topics after they are read for long enough if changed manually" do
TopicUser.change(
new_user,
topic,
notification_level: TopicUser.notification_levels[:regular],
)
TopicUser.update_last_read(
new_user,
topic,
2,
2,
SiteSetting.default_other_auto_track_topics_after_msecs + 1,
)
expect(topic_new_user.notification_level).to eq(TopicUser.notification_levels[:regular])
end
it "should not automatically track PMs" do
new_user.user_option.update!(auto_track_topics_after_msecs: 0)
another_user = Fabricate(:user)
Group.refresh_automatic_groups!
pm = Fabricate(:private_message_topic, user: another_user)
pm.invite(another_user, new_user.username)
TopicUser.track_visit!(pm.id, new_user.id)
TopicUser.update_last_read(new_user, pm.id, 2, 2, 1000)
expect(TopicUser.get(pm, new_user).notification_level).to eq(
TopicUser.notification_levels[:watching],
)
end
end
end
describe "change a flag" do
it "only inserts a row once, even on repeated calls" do
topic
user
expect {
TopicUser.change(user, topic.id, total_msecs_viewed: 1)
TopicUser.change(user, topic.id, total_msecs_viewed: 2)
TopicUser.change(user, topic.id, total_msecs_viewed: 3)
}.to change(TopicUser, :count).by(1)
end
describe "after creating a row" do
before { ensure_topic_user }
it "has a lookup" do
expect(TopicUser.lookup_for(user, [topic])).to be_present
end
it "has a key in the lookup for this forum topic" do
expect(TopicUser.lookup_for(user, [topic]).has_key?(topic.id)).to eq(true)
end
end
end
it "can scope by tracking" do
TopicUser.create!(
user_id: 1,
topic_id: 1,
notification_level: TopicUser.notification_levels[:tracking],
)
TopicUser.create!(
user_id: 2,
topic_id: 1,
notification_level: TopicUser.notification_levels[:watching],
)
TopicUser.create!(
user_id: 3,
topic_id: 1,
notification_level: TopicUser.notification_levels[:regular],
)
expect(TopicUser.tracking(1).count).to eq(2)
expect(TopicUser.tracking(10).count).to eq(0)
end
it "is able to self heal" do
p1 = Fabricate(:post)
p2 = Fabricate(:post, user: p1.user, topic: p1.topic, post_number: 2)
p1.topic.notifier.watch_topic!(p1.user_id)
DB.exec(
"UPDATE topic_users set last_read_post_number=0
WHERE topic_id = :topic_id AND user_id = :user_id",
topic_id: p1.topic_id,
user_id: p1.user_id,
)
[p1, p2].each do |p|
PostTiming.create(
topic_id: p.topic_id,
post_number: p.post_number,
user_id: p.user_id,
msecs: 100,
)
end
TopicUser.ensure_consistency!
tu = TopicUser.find_by(user_id: p1.user_id, topic_id: p1.topic_id)
expect(tu.last_read_post_number).to eq(p2.post_number)
end
describe "mailing_list_mode" do
it "will receive email notification for every topic" do
user1 = Fabricate(:user)
Jobs.run_immediately!
SiteSetting.disable_mailing_list_mode = false
SiteSetting.default_email_mailing_list_mode = true
SiteSetting.default_email_mailing_list_mode_frequency = 1
user2 = Fabricate(:user)
post = create_post
user3 = Fabricate(:user)
create_post(topic_id: post.topic_id)
# mails posts from earlier topics
tu = TopicUser.find_by(user_id: user3.id, topic_id: post.topic_id)
expect(tu.last_emailed_post_number).to eq(2)
# mails nothing to random users
tu = TopicUser.find_by(user_id: user1.id, topic_id: post.topic_id)
expect(tu).to eq(nil)
# mails other user
tu = TopicUser.find_by(user_id: user2.id, topic_id: post.topic_id)
expect(tu.last_emailed_post_number).to eq(2)
end
end
it "correctly triggers an event on first visit" do
begin
tracked_user = Fabricate(:user)
post = create_post
called = 0
visits = []
user_first_visit = ->(topic_id, user_id) do
visits << "#{topic_id}-#{user_id}"
called += 1
end
DiscourseEvent.on(:topic_first_visited_by_user, &user_first_visit)
expect(called).to eq(0)
TopicUser.change(tracked_user, post.topic.id, total_msecs_viewed: 1)
expect(visits).to eq(["#{post.topic.id}-#{tracked_user.id}"])
expect(called).to eq(1)
TopicUser.change(tracked_user, post.topic.id, total_msecs_viewed: 2)
expect(called).to eq(1)
ensure
DiscourseEvent.off(:topic_first_visited_by_user, &user_first_visit)
end
end
end