0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-09 21:45:25 +08:00
discourse/plugins/discourse-topic-voting/spec/requests/votes_controller_spec.rb
Régis Hanol 73eb2a0b66
FIX: Show voters for closed topics in topic-voting (#40314)
Previously, opening the "who voted" popup on a closed topic returned an
empty list even though the vote count still showed the correct total.
The same was true for any topic whose votes had been archived (e.g.
moved out of a voting category, or trashed).

This was a regression from #39394, which added `votes.active` to
`Topic#who_voted` alongside the same filter applied to "my votes" and
`/topics/voted-by/:username`. That filter is correct for those per-user
listings — you don't want closed-topic votes polluting "your votes" —
but the `who_voted` popup is about historical participation on a
specific topic, not per-user vote accounting.

This change drops `.active` from `Topic#who_voted` so archived votes are
included, restoring the pre-regression behaviour. The accounting filters
in `Votes::Remove`, `User#topics_with_active_vote`, the voted-topics
query, and the `current_user_voted` flag are unchanged.

https://meta.discourse.org/t/403286

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 19:18:25 +02:00

200 lines
6.7 KiB
Ruby
Vendored

# frozen_string_literal: true
describe DiscourseTopicVoting::VotesController do
let(:user) { Fabricate(:user) }
let(:category) { Fabricate(:category) }
let(:topic) { Fabricate(:topic, category_id: category.id) }
before do
DiscourseTopicVoting::CategorySetting.create!(category:)
Category.reset_voting_cache
SiteSetting.topic_voting_show_who_voted = true
SiteSetting.topic_voting_enabled = true
sign_in(user)
end
it "does not allow voting if voting is not enabled" do
SiteSetting.topic_voting_enabled = false
post "/voting/vote.json", params: { topic_id: topic.id }
expect(response.status).to eq(404)
end
it "returns not found for a stale topic id when voting" do
topic.destroy
post "/voting/vote.json", params: { topic_id: topic.id }
expect(response.status).to eq(404)
end
it "can correctly show deal with voting workflow" do
SiteSetting.public_send "topic_voting_tl#{user.trust_level}_vote_limit=", 2
post "/voting/vote.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
post "/voting/vote.json", params: { topic_id: topic.id }
expect(response.status).to eq(403)
expect(topic.reload.vote_count).to eq(1)
expect(user.reload.vote_count).to eq(1)
get "/voting/who.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
json = JSON.parse(response.body)
expect(json.length).to eq(1)
expect(json.first.keys.sort).to eq(%w[avatar_template id name username])
expect(json.first["id"]).to eq(user.id)
post "/voting/unvote.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
expect(topic.reload.vote_count).to eq(0)
expect(user.reload.vote_count).to eq(0)
end
it "returns 403 when the user already voted" do
DiscourseTopicVoting::Vote.create!(user:, topic:)
post "/voting/vote.json", params: { topic_id: topic.id }
expect(response.status).to eq(403)
end
it "returns 403 with voting payload when the user reached the vote limit" do
SiteSetting.public_send("topic_voting_tl#{user.trust_level}_vote_limit=", 0)
post "/voting/vote.json", params: { topic_id: topic.id }
expect(response.status).to eq(403)
json = response.parsed_body
expect(json["can_vote"]).to eq(false)
expect(json["vote_limit"]).to eq(0)
expect(json["vote_count"]).to eq(0)
expect(json["votes_left"]).to eq(0)
expect(json["alert"]).to eq(true)
end
context "when vote limits are disabled" do
before do
SiteSetting.topic_voting_enable_vote_limits = false
SiteSetting.public_send("topic_voting_tl#{user.trust_level}_vote_limit=", 0)
end
it "allows voting and returns nil for limit fields" do
post "/voting/vote.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["can_vote"]).to eq(true)
expect(json["vote_limit"]).to be_nil
expect(json["votes_left"]).to be_nil
expect(json["alert"]).to eq(false)
end
it "returns nil for limit fields on unvote" do
DiscourseTopicVoting::Vote.create!(user:, topic:)
post "/voting/unvote.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["vote_limit"]).to be_nil
expect(json["votes_left"]).to be_nil
end
end
it "triggers a topic_upvote webhook when voting" do
Fabricate(:topic_voting_web_hook)
post "/voting/vote.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
job_args = Jobs::EmitWebHookEvent.jobs[0]["args"].first
expect(job_args["event_name"]).to eq("topic_upvote")
payload = JSON.parse(job_args["payload"])
expect(payload["topic_id"]).to eq(topic.id)
expect(payload["topic_slug"]).to eq(topic.slug)
expect(payload["voter_id"]).to eq(user.id)
expect(payload["vote_count"]).to eq(1)
end
it "triggers a topic_unvote webhook when unvoting" do
DiscourseTopicVoting::Vote.create!(user:, topic:)
topic.update_vote_count
Fabricate(:topic_voting_web_hook)
post "/voting/unvote.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
job_args = Jobs::EmitWebHookEvent.jobs[0]["args"].first
expect(job_args["event_name"]).to eq("topic_unvote")
payload = JSON.parse(job_args["payload"])
expect(payload["topic_id"]).to eq(topic.id)
expect(payload["topic_slug"]).to eq(topic.slug)
expect(payload["voter_id"]).to eq(user.id)
expect(payload["vote_count"]).to eq(0)
end
it "does not remove an archived vote when unvoting" do
DiscourseTopicVoting::Vote.create!(user:, topic:, archive: true)
topic.update_vote_count
post "/voting/unvote.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
expect(DiscourseTopicVoting::Vote.where(user:, topic:, archive: true).count).to eq(1)
expect(topic.reload.vote_count).to eq(1)
end
it "returns 200 when there is no active vote to remove" do
post "/voting/unvote.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
expect(response.parsed_body["vote_count"]).to eq(0)
end
it "limits who-voted previews to VOTE_PREVIEW_LIMIT users by default" do
stub_const(DiscourseTopicVoting, "VOTER_PREVIEW_LIMIT", 10) do
Fabricate
.times(11, :user)
.each { |voter| DiscourseTopicVoting::Vote.create!(user: voter, topic:) }
get "/voting/who.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
expect(response.parsed_body.length).to eq(DiscourseTopicVoting::VOTER_PREVIEW_LIMIT)
end
end
it "still returns voters after the topic has been closed" do
voter = Fabricate(:user)
DiscourseTopicVoting::Vote.create!(user: voter, topic:)
topic.update_vote_count
Jobs.run_immediately!
topic.update_status("closed", true, Discourse.system_user)
get "/voting/who.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
expect(response.parsed_body.pluck("id")).to eq([voter.id])
end
it "includes archived votes and honors a smaller who-voted limit" do
older_voter = Fabricate(:user)
newer_voter = Fabricate(:user)
archived_voter = Fabricate(:user)
DiscourseTopicVoting::Vote.create!(user: older_voter, topic:, created_at: 2.hours.ago)
DiscourseTopicVoting::Vote.create!(user: newer_voter, topic:, created_at: 1.hour.ago)
DiscourseTopicVoting::Vote.create!(
user: archived_voter,
topic:,
archive: true,
created_at: Time.zone.now,
)
get "/voting/who.json", params: { topic_id: topic.id, limit: 1 }
expect(response.status).to eq(200)
expect(response.parsed_body.pluck("id")).to eq([archived_voter.id])
end
end