discourse/plugins/discourse-gamification/spec/requests/admin_gamification_leaderboard_controller_spec.rb
Rafael dos Santos Silva 848c19ae48
FEATURE: Per-leaderboard scorable weights and category filters (#39062)
## Summary

- Moves scorable weight configuration and category filtering from global
site settings to individual leaderboards
- Each leaderboard can override any of the 15 score weight values and
specify its own scorable categories
- Score calculation now runs per leaderboard into a new
`gamification_leaderboard_scores` table
- Admin UI gains a "Scoring configuration" section on the leaderboard
edit form

## Details

Previously all leaderboards shared the same pre-calculated scores from
global site settings. This made it impossible to have e.g. a
"posts-only" leaderboard alongside a "likes-focused" one.

Now each leaderboard can optionally override:
- **Score weights**: Set per-action point values (empty = inherit global
default, 0 = disabled)
- **Scorable categories**: Restrict which categories count toward
scoring (empty = inherit global setting)

The old `gamification_scores` table is no longer written to — a
post-deploy migration to drop it will follow separately.

## Test plan

- [ ] Create a leaderboard with default scoring — scores match current
behavior
- [ ] Create a leaderboard with custom weights (e.g. `post_created` =
10, all others empty) — only overridden weights differ
- [ ] Set a weight to 0 — that scorable is disabled for the leaderboard
- [ ] Set per-leaderboard categories — only those categories count
- [ ] Clear categories — inherits global `scorable_categories` setting
- [ ] Existing leaderboards with no overrides behave identically after
upgrade
- [ ] Directory scores and user card scores work correctly (read from
default leaderboard)
- [ ] `bin/rspec plugins/discourse-gamification/spec/` — 120 examples, 0
failures
2026-04-23 11:23:01 -03:00

107 lines
3.5 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe DiscourseGamification::AdminGamificationLeaderboardController do
fab!(:admin)
before do
SiteSetting.discourse_gamification_enabled = true
sign_in(admin)
end
describe "#create" do
it "creates leaderboard and enqueues score recalculation" do
expect(Jobs::RecalculateLeaderboardScores.jobs.size).to eq(0)
expect do
post "/admin/plugins/gamification/leaderboard.json",
params: {
name: "Test",
created_by_id: admin.id,
}
end.to change { DiscourseGamification::GamificationLeaderboard.count }.by(1)
expect(response.status).to eq(200)
expect(response.parsed_body).to include("name" => "Test", "created_by_id" => admin.id)
job_data = Jobs::RecalculateLeaderboardScores.jobs.first["args"].first
expect(job_data).to include("leaderboard_id" => response.parsed_body["id"])
end
end
describe "#update" do
it "updates leaderboard and enqueues positions refresh" do
leaderboard = Fabricate(:gamification_leaderboard, created_by_id: admin.id)
expect(Jobs::RefreshLeaderboardPositions.jobs.size).to eq(0)
put "/admin/plugins/gamification/leaderboard/#{leaderboard.id}.json",
params: {
name: "New Name",
}
expect(response.status).to eq(200)
expect(leaderboard.reload.name).to eq("New Name")
job_data = Jobs::RefreshLeaderboardPositions.jobs.first["args"].first
expect(job_data).to include("leaderboard_id" => leaderboard.id)
end
end
describe "#show" do
it "returns admin-only scoring configuration" do
scorable_category_id = 123
leaderboard =
Fabricate(
:gamification_leaderboard,
created_by_id: admin.id,
score_overrides: {
"like_received" => 10,
},
scorable_category_ids: [scorable_category_id],
)
get "/admin/plugins/discourse-gamification/leaderboards/#{leaderboard.id}.json"
expect(response.status).to eq(200)
expect(response.parsed_body["leaderboard"]).to include(
"score_overrides" => {
"like_received" => 10,
},
"scorable_category_ids" => [scorable_category_id],
)
end
end
describe "destroy" do
it "deletes leaderboard and enqueues deletion of positions" do
leaderboard = Fabricate(:gamification_leaderboard, created_by_id: admin.id)
delete "/admin/plugins/gamification/leaderboard/#{leaderboard.id}.json"
expect { leaderboard.reload }.to raise_error(ActiveRecord::RecordNotFound)
job_data = Jobs::DeleteLeaderboardPositions.jobs.first["args"].first
expect(job_data).to include("leaderboard_id" => leaderboard.id)
end
end
describe "#recalculate_scores" do
it "enqueues the job with 'since' date" do
put "/admin/plugins/gamification/recalculate-scores.json", params: { from_date: 10.days.ago }
expect(response.status).to eq(200)
expect(Jobs::RecalculateScores.jobs.size).to eq(1)
job_data = Jobs::RecalculateScores.jobs.first["args"].first
expect(Date.parse(job_data["since"])).to eq(10.days.ago.midnight)
end
it "does not enqueue the job with invalid 'since' date" do
put "/admin/plugins/gamification/recalculate-scores.json",
params: {
from_date: 1.day.from_now,
}
expect(response.status).to eq(400)
expect(Jobs::RecalculateScores.jobs.size).to eq(0)
end
end
end