discourse/spec/jobs/grant_anniversary_badges_spec.rb
Régis Hanol b704e338ef
DEV: extract anniversary badge query (#19716)
So it can easily be overwritten in a plugin for example.

### Added more tests to provide better coverage

We previously only had `u.silenced_till IS NULL` but I made it consistent with pretty much every other places where we check for "active" users.

These two new lines do change the query a tiny bit though. 

**Before** 

- You could not get the badge if you were currently silenced (no matter what period is being checked)
- You could get the badge if you were suspended 😬 

**After**

- You can't get the badge if you were silenced during the past year
- You can't get the badge if you were suspended during the past year


### Improved the performance of the query by using `NOT EXISTS` instead of `LEFT JOIN / COUNT() = 0`

There is no difference in behaviour between 

```sql
LEFT JOIN user_badges AS ub ON ub.user_id = u.id AND ...
[...]
HAVING COUNT(ub.*) = 0
```

and

```sql
NOT EXISTS (SELECT 1 FROM user_badges AS ub WHERE ub.user_id = u.id AND ...)
```

The only difference is performance-wise. The `NOT EXISTS` is 10-30% faster on very large databases (aka. posts and users in X millions). I checked on 3 of the largest datasets I could find.
2023-01-16 11:55:00 +01:00

169 lines
5.4 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Jobs::GrantAnniversaryBadges do
let(:granter) { described_class.new }
it "doesn't award to a user who is less than a year old" do
user = Fabricate(:user, created_at: 1.month.ago)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award to a bot" do
user = Fabricate(:bot, created_at: 400.days.ago)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award to an inactive user" do
user = Fabricate(:user, created_at: 400.days.ago, active: false)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award to a silenced user" do
user = Fabricate(:user, created_at: 400.days.ago, silenced_till: 1.year.from_now)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award to a suspended user" do
user = Fabricate(:user, created_at: 400.days.ago, suspended_till: 1.year.from_now)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award to a staged user" do
user = Fabricate(:user, created_at: 400.days.ago, staged: true)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award to an anonymous user" do
user = Fabricate(:anonymous, created_at: 400.days.ago)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award when a post is deleted" do
user = Fabricate(:user, created_at: 400.days.ago)
Fabricate(:post, user: user, created_at: 1.week.ago, deleted_at: 1.day.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award when a post is hidden" do
user = Fabricate(:user, created_at: 400.days.ago)
Fabricate(:post, user: user, created_at: 1.week.ago, hidden: true)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award to PMs" do
user = Fabricate(:user, created_at: 400.days.ago)
Fabricate(:private_message_post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award to a user without a post" do
user = Fabricate(:user, created_at: 1.month.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge).to be_blank
end
it "doesn't award when badges are disabled" do
SiteSetting.enable_badges = false
user = Fabricate(:user, created_at: 400.days.ago)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge.count).to eq(0)
end
it "awards the badge to a user with a post active for a year" do
user = Fabricate(:user, created_at: 400.days.ago)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge.count).to eq(1)
end
context "with repeated grants" do
it "won't award twice in the same year" do
user = Fabricate(:user, created_at: 400.days.ago)
Fabricate(:post, user: user, created_at: 1.week.ago)
granter.execute({})
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge.count).to eq(1)
end
it "will award again if a year has passed" do
user = Fabricate(:user, created_at: 800.days.ago)
Fabricate(:post, user: user, created_at: 450.days.ago)
freeze_time(400.days.ago) { granter.execute({}) }
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge.count).to eq(1)
Fabricate(:post, user: user, created_at: 50.days.ago)
granter.execute({})
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge.count).to eq(2)
end
it "supports date ranges" do
user = Fabricate(:user, created_at: 3.years.ago)
Fabricate(:post, user: user, created_at: 750.days.ago)
granter.execute(start_date: 800.days.ago)
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge.count).to eq(1)
Fabricate(:post, user: user, created_at: 50.days.ago)
granter.execute(start_date: 800.days.ago)
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge.count).to eq(1)
granter.execute(start_date: 60.days.ago)
badge = user.user_badges.where(badge_id: Badge::Anniversary)
expect(badge.count).to eq(2)
end
end
end