mirror of
https://ghfast.top/https://github.com/discourse/discourse-follow.git
synced 2026-07-15 11:36:38 +08:00
The follow button was completely hidden for suspended, staged, and bot
users. This meant that if a user followed someone who was later
suspended, they had no way to unfollow them without an admin
unsuspending and re-suspending the account.
The root cause was that `follow-button.gjs` unconditionally hid the
button based on the target user's state, with no distinction between
the follow and unfollow actions. On top of that, the `can_follow`
serializer field only checked `allow_people_to_follow_me` and not the
other states (suspended, staged, bot, self), so the client had to
duplicate those checks.
This commit introduces a `Guardian#can_follow?` method as the single
source of truth for follow permissions. The serializer delegates to it,
and the client simply shows the button when the user is already followed
OR can be followed. The `Updater` also uses the guardian as a first
check, then determines the specific error reason on failure to provide
meaningful error messages.
Other cleanups:
- Replace manual `raise if !current_user` checks with `requires_login`
- Fix mismatched i18n keys (`cannot_follow_*` → `user_cannot_follow_*`)
- Fix typo in locale ("suspend" → "suspended")
https://meta.discourse.org/t/304256
189 lines
8 KiB
Ruby
189 lines
8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
describe ::Follow::Updater do
|
|
before { SiteSetting.discourse_follow_enabled = true }
|
|
|
|
def new_updater(follower, target)
|
|
::Follow::Updater.new(follower, target)
|
|
end
|
|
|
|
fab!(:user1, :user)
|
|
fab!(:user2, :user)
|
|
fab!(:user3, :user)
|
|
|
|
it "does not allow following a bot user" do
|
|
expect { new_updater(user1, Discourse.system_user).watch_follow }.to raise_error(
|
|
Discourse::InvalidAccess,
|
|
) { |error| expect(error.custom_message).to eq("follow.user_cannot_follow_bot") }
|
|
end
|
|
|
|
it "does not allow following a staged user" do
|
|
user2.update!(staged: true)
|
|
expect { new_updater(user1, user2).watch_follow }.to raise_error(
|
|
Discourse::InvalidAccess,
|
|
) { |error| expect(error.custom_message).to eq("follow.user_cannot_follow_staged") }
|
|
end
|
|
|
|
it "does not allow following a suspended user" do
|
|
user2.update!(suspended_till: 10.hours.from_now)
|
|
expect { new_updater(user1, user2).watch_follow }.to raise_error(
|
|
Discourse::InvalidAccess,
|
|
) { |error| expect(error.custom_message).to eq("follow.user_cannot_follow_suspended") }
|
|
end
|
|
|
|
it "does not allow a user to follow themself" do
|
|
expect { new_updater(user1, user1).watch_follow }.to raise_error(
|
|
Discourse::InvalidAccess,
|
|
) { |error| expect(error.custom_message).to eq("follow.user_cannot_follow_themself") }
|
|
end
|
|
|
|
it "does not allow following a user who has disabled follows" do
|
|
user2.custom_fields["allow_people_to_follow_me"] = false
|
|
user2.save!
|
|
expect { new_updater(user1, user2).watch_follow }.to raise_error(
|
|
Discourse::InvalidAccess,
|
|
) do |error|
|
|
expect(error.custom_message).to eq("follow.user_does_not_allow_follow")
|
|
expect(error.custom_message_params).to eq({ username: user2.username })
|
|
end
|
|
end
|
|
|
|
it "does not allow following a user who has hidden their profile" do
|
|
user2.user_option.update!(hide_profile: true)
|
|
expect { new_updater(user1, user2).watch_follow }.to raise_error(
|
|
Discourse::InvalidAccess,
|
|
) do |error|
|
|
expect(error.custom_message).to eq("follow.user_does_not_allow_follow")
|
|
expect(error.custom_message_params).to eq({ username: user2.username })
|
|
end
|
|
end
|
|
|
|
it "works" do
|
|
new_updater(user1, user2).watch_follow
|
|
expect(user1.following.pluck(:id)).to contain_exactly(user2.id)
|
|
expect(user2.followers.pluck(:id)).to contain_exactly(user1.id)
|
|
relation = user1.following_relations.find_by(user_id: user2.id)
|
|
expect(relation.level).to eq(Follow::Notification.levels[:watching])
|
|
end
|
|
|
|
it "allows unfollowing a suspended user" do
|
|
new_updater(user1, user2).watch_follow
|
|
user2.update!(suspended_till: 10.hours.from_now)
|
|
new_updater(user1, user2).unfollow
|
|
expect(user1.reload.following.pluck(:id)).to eq([])
|
|
end
|
|
|
|
it "allows unfollowing a staged user" do
|
|
new_updater(user1, user2).watch_follow
|
|
user2.update!(staged: true)
|
|
new_updater(user1, user2).unfollow
|
|
expect(user1.reload.following.pluck(:id)).to eq([])
|
|
end
|
|
|
|
it "sends a notification to the followed user if they opt-in this type " \
|
|
"of notifications and follower allows this notification" do
|
|
user1.custom_fields[:notify_followed_user_when_followed] = true
|
|
user1.save!
|
|
user2.custom_fields[:notify_me_when_followed] = true
|
|
user2.save!
|
|
user2.notifications.destroy_all
|
|
new_updater(user1, user2).watch_follow
|
|
notification =
|
|
user2.reload.notifications.find_by(notification_type: Notification.types[:following])
|
|
expect(notification).to be_present
|
|
data = JSON.parse(notification.data)
|
|
expect(data["display_username"]).to eq(user1.username)
|
|
expect(user1.following.pluck(:id)).to contain_exactly(user2.id)
|
|
expect(user2.followers.pluck(:id)).to contain_exactly(user1.id)
|
|
relation = user1.following_relations.find_by(user_id: user2.id)
|
|
expect(relation.level).to eq(Follow::Notification.levels[:watching])
|
|
end
|
|
|
|
it "does not send a notification to the followed user if they opt-out of " \
|
|
"this type of notifications" do
|
|
user1.custom_fields[:notify_followed_user_when_followed] = true
|
|
user1.save!
|
|
user2.custom_fields[:notify_me_when_followed] = false
|
|
user2.save!
|
|
new_updater(user1, user2).watch_follow
|
|
expect(user2.reload.notifications.count).to eq(0)
|
|
expect(user1.following.pluck(:id)).to contain_exactly(user2.id)
|
|
expect(user2.followers.pluck(:id)).to contain_exactly(user1.id)
|
|
relation = user1.following_relations.find_by(user_id: user2.id)
|
|
expect(relation.level).to eq(Follow::Notification.levels[:watching])
|
|
end
|
|
|
|
it "does not send a notification to the followed user if the follower " \
|
|
"does not allow this notification to be sent out" do
|
|
user1.custom_fields[:notify_followed_user_when_followed] = false
|
|
user1.save!
|
|
user2.custom_fields[:notify_me_when_followed] = true
|
|
user2.save!
|
|
new_updater(user1, user2).watch_follow
|
|
expect(user2.reload.notifications.count).to eq(0)
|
|
expect(user1.following.pluck(:id)).to contain_exactly(user2.id)
|
|
expect(user2.followers.pluck(:id)).to contain_exactly(user1.id)
|
|
relation = user1.following_relations.find_by(user_id: user2.id)
|
|
expect(relation.level).to eq(Follow::Notification.levels[:watching])
|
|
end
|
|
|
|
it "does not send a notification to the followed user if the " \
|
|
"follow_notifications_enabled site setting is off" do
|
|
SiteSetting.follow_notifications_enabled = false
|
|
user1.custom_fields[:notify_followed_user_when_followed] = true
|
|
user1.save!
|
|
user2.custom_fields[:notify_me_when_followed] = true
|
|
user2.save!
|
|
new_updater(user1, user2).watch_follow
|
|
expect(user2.reload.notifications.count).to eq(0)
|
|
expect(user1.following.pluck(:id)).to contain_exactly(user2.id)
|
|
expect(user2.followers.pluck(:id)).to contain_exactly(user1.id)
|
|
relation = user1.following_relations.find_by(user_id: user2.id)
|
|
expect(relation.level).to eq(Follow::Notification.levels[:watching])
|
|
end
|
|
|
|
it "does not send a notification to the followed user if there is " \
|
|
"a follow notification from the same follower within the last 24 hours" do
|
|
user1.custom_fields[:notify_followed_user_when_followed] = true
|
|
user1.save!
|
|
user2.custom_fields[:notify_me_when_followed] = true
|
|
user2.save!
|
|
|
|
expect do new_updater(user1, user2).watch_follow end.to change {
|
|
user2.reload.notifications.where(notification_type: Notification.types[:following]).count
|
|
}.by(1)
|
|
expect(user1.following.pluck(:id)).to contain_exactly(user2.id)
|
|
expect(user2.followers.pluck(:id)).to contain_exactly(user1.id)
|
|
relation = user1.following_relations.find_by(user_id: user2.id)
|
|
expect(relation.level).to eq(Follow::Notification.levels[:watching])
|
|
|
|
freeze_time 6.hours.from_now
|
|
|
|
new_updater(user1, user2).unfollow
|
|
expect(user1.reload.following.pluck(:id)).to eq([])
|
|
expect(user2.reload.followers.pluck(:id)).to eq([])
|
|
expect(
|
|
user2.notifications.where(notification_type: Notification.types[:following]).count,
|
|
).to eq(1) # unchanged
|
|
|
|
# follow again
|
|
expect do new_updater(user1, user2).watch_follow end.not_to change {
|
|
user2.reload.notifications.where(notification_type: Notification.types[:following]).count
|
|
}
|
|
expect(user1.following.pluck(:id)).to contain_exactly(user2.id)
|
|
expect(user2.followers.pluck(:id)).to contain_exactly(user1.id)
|
|
relation = user1.following_relations.find_by(user_id: user2.id)
|
|
expect(relation.level).to eq(Follow::Notification.levels[:watching])
|
|
|
|
# following another user will result in a notification
|
|
user3.custom_fields[:notify_me_when_followed] = true
|
|
user3.save!
|
|
expect do new_updater(user1, user3).watch_follow end.to change {
|
|
user3.reload.notifications.where(notification_type: Notification.types[:following]).count
|
|
}.by(1)
|
|
expect(user1.following.pluck(:id)).to contain_exactly(user2.id, user3.id)
|
|
expect(user3.followers.pluck(:id)).to contain_exactly(user1.id)
|
|
relation = user1.following_relations.find_by(user_id: user3.id)
|
|
expect(relation.level).to eq(Follow::Notification.levels[:watching])
|
|
end
|
|
end
|