From 5a99939d93578422f04d5db41bf8b1d79e8f8e36 Mon Sep 17 00:00:00 2001
From: Alex Manolache <a.d.manolache@student.tudelft.nl>
Date: Wed, 22 Nov 2023 16:38:23 +0100
Subject: [PATCH] Change upgrade badge logic and update tests

---
 app/models/badges/badge_famous_question.rb    | 29 +++++++++------
 app/models/badges/badge_great_answer.rb       | 24 ++++++++-----
 app/models/badges/badge_great_question.rb     | 35 +++++++++++--------
 test/fixtures/users.yml                       |  2 +-
 .../badges/badge_famous_question_test.rb      | 16 +++++++++
 test/models/badges/badge_great_answer_test.rb | 32 +++++++++++++++++
 .../badges/badge_great_question_test.rb       | 16 +++++++++
 7 files changed, 120 insertions(+), 34 deletions(-)

diff --git a/app/models/badges/badge_famous_question.rb b/app/models/badges/badge_famous_question.rb
index 91c11076a..bbc3633f6 100644
--- a/app/models/badges/badge_famous_question.rb
+++ b/app/models/badges/badge_famous_question.rb
@@ -37,7 +37,22 @@ class BadgeFamousQuestion
       notify(content, recv_user, new_user_badge)
       true
     else
-      upgrade_badge(existing_badge, post, recv_user)
+      check_upgrade(existing_badge, post, recv_user)
+    end
+  end
+
+  # @param [UserBadge] existing_badge
+  # @param [Post] post
+  # @param [User] recv_user
+  # @return
+  def self.check_upgrade(existing_badge, post, recv_user)
+    old_badge_type = UserBadge.badge_types[existing_badge.badge_type]
+    badge_type = upgrade_badge(existing_badge, post)
+    return false unless badge_type
+
+    if old_badge_type != badge_type
+      content = "Your badge has been upgraded to #{badge_type}!"
+      notify(content, recv_user, existing_badge)
     end
   end
 
@@ -45,23 +60,17 @@ class BadgeFamousQuestion
   # @param [Post] post
   # @param [User] recv_user
   # @return boolean
-  def self.upgrade_badge(user_badge, post, recv_user)
-    # Check if the badge should be turned into a golden badge
+  def self.upgrade_badge(user_badge, post)
     if post.views_count >= BADGE_AWARD_THRESHOLDS[2]
       user_badge.update(badge_type: :gold)
       return false if user_badge.errors.any?
 
-      content = 'Your badge has been upgraded to gold!'
-      notify(content, recv_user, user_badge)
-      true
-      # Check if the badge should be turned into a silver badge
+      return UserBadge.badge_types[:gold]
     elsif post.views_count >= BADGE_AWARD_THRESHOLDS[1]
       user_badge.update(badge_type: :silver)
       return false if user_badge.errors.any?
 
-      content = 'Your badge has been upgraded to silver!'
-      notify(content, recv_user, user_badge)
-      true
+      return UserBadge.badge_types[:silver]
     end
     false
   end
diff --git a/app/models/badges/badge_great_answer.rb b/app/models/badges/badge_great_answer.rb
index 3546ecf84..2b278943f 100644
--- a/app/models/badges/badge_great_answer.rb
+++ b/app/models/badges/badge_great_answer.rb
@@ -34,7 +34,18 @@ class BadgeGreatAnswer
       notify(content, recv_user, new_user_badge)
       true
     else
-      upgrade_badge(existing_badge, post, recv_user)
+      check_upgrade(existing_badge, post, recv_user)
+    end
+  end
+
+  def self.check_upgrade(existing_badge, post, recv_user)
+    old_badge_type = UserBadge.badge_types[existing_badge.badge_type]
+    badge_type = upgrade_badge(existing_badge, post)
+    return false unless badge_type
+
+    if old_badge_type != badge_type
+      content = "Your badge has been upgraded to #{badge_type}!"
+      notify(content, recv_user, existing_badge)
     end
   end
 
@@ -42,23 +53,18 @@ class BadgeGreatAnswer
   # @param [Post] post
   # @param [User] recv_user
   # @return boolean
-  def self.upgrade_badge(user_badge, post, recv_user)
+  def self.upgrade_badge(user_badge, post)
     # Check if the badge should be turned into a golden badge
     if post.score >= BADGE_AWARD_THRESHOLDS[2]
       user_badge.update(badge_type: :gold)
       return false if user_badge.errors.any?
 
-      content = 'Your badge has been upgraded to gold!'
-      notify(content, recv_user, user_badge)
-      true
-      # Check if the badge should be turned into a silver badge
+      return UserBadge.badge_types[:gold]
     elsif post.score >= BADGE_AWARD_THRESHOLDS[1]
       user_badge.update(badge_type: :silver)
       return false if user_badge.errors.any?
 
-      content = 'Your badge has been upgraded to silver!'
-      notify(content, recv_user, user_badge)
-      true
+      return UserBadge.badge_types[:silver]
     end
     false
   end
diff --git a/app/models/badges/badge_great_question.rb b/app/models/badges/badge_great_question.rb
index d3f443760..59fd6e821 100644
--- a/app/models/badges/badge_great_question.rb
+++ b/app/models/badges/badge_great_question.rb
@@ -35,33 +35,42 @@ class BadgeGreatQuestion
       content = "You have been awarded the badge '#{new_user_badge.badge.title}'
       for your question '#{post.title}'"
       notify(content, recv_user, new_user_badge)
-      true
     else
-      upgrade_badge(existing_badge, post, recv_user)
+      check_upgrade(existing_badge, post, recv_user)
+    end
+    true
+  end
+
+  # @param [UserBadge] existing_badge
+  # @param [Post] post
+  # @param [User] recv_user
+  # @return
+  def self.check_upgrade(existing_badge, post, recv_user)
+    old_badge_type = UserBadge.badge_types[existing_badge.badge_type]
+    badge_type = upgrade_badge(existing_badge, post)
+    return false unless badge_type
+
+    if old_badge_type != badge_type
+      content = "Your badge has been upgraded to #{badge_type}!"
+      notify(content, recv_user, existing_badge)
     end
   end
 
   # @param [UserBadge] user_badge
   # @param [Post] post
   # @param [User] recv_user
-  # @return boolean
-  def self.upgrade_badge(user_badge, post, recv_user)
-    # Check if the badge should be turned into a golden badge
+  # @return boolean OR string
+  def self.upgrade_badge(user_badge, post)
     if post.score >= BADGE_AWARD_THRESHOLDS[2]
       user_badge.update(badge_type: :gold)
       return false if user_badge.errors.any?
 
-      content = 'Your badge has been upgraded to gold!'
-      notify(content, recv_user, user_badge)
-      true
-    # Check if the badge should be turned into a silver badge
+      return UserBadge.badge_types[:gold]
     elsif post.score >= BADGE_AWARD_THRESHOLDS[1]
       user_badge.update(badge_type: :silver)
       return false if user_badge.errors.any?
 
-      content = 'Your badge has been upgraded to silver!'
-      notify(content, recv_user, user_badge)
-      true
+      return UserBadge.badge_types[:silver]
     end
     false
   end
@@ -71,8 +80,6 @@ class BadgeGreatQuestion
   def self.eligible?(post)
     post.reload
     post.question? && post.score >= BADGE_AWARD_THRESHOLDS[0]
-
-    # !recv_user.user_badges.exists?(badge_id: badge_id, badge_source: post)
   end
 
   # @param string message
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
index e2a91b308..fb5d54c06 100644
--- a/test/fixtures/users.yml
+++ b/test/fixtures/users.yml
@@ -122,7 +122,7 @@ enabled_2fa:
   two_factor_token: WT65ANYXBB2SBR7III7IVWNJDS4PQF2T
   backup_2fa_code: M8lENyehyCvo9F9MbyTl1aOL
 
-<% (1..200).each do |i| %>
+<% (1..210).each do |i| %>
 user_test_<%= i %>:
   email: user_test_<%= i %>@qpixel-test.net
   encrypted_password: '$2a$11$roUHXKxecjyQ72Qn7DWs3.9eRCCoRn176kX/UNb/xiue3aGqf7xEW'
diff --git a/test/models/badges/badge_famous_question_test.rb b/test/models/badges/badge_famous_question_test.rb
index 9a2dfd6ac..17112f478 100644
--- a/test/models/badges/badge_famous_question_test.rb
+++ b/test/models/badges/badge_famous_question_test.rb
@@ -38,6 +38,22 @@ class BadgeFamousQuestionTest < ActionController::TestCase
     assert_equal @user.user_badges.where(badge_id: @badge.id, badge_source: @post).count, 0
   end
 
+  test 'user receives only one notification per badge tier' do
+    assert_equal @user.notifications.count, 2
+
+    create_views(1, BadgeFamousQuestion.badge_award_thresholds[0], @post)
+    assert_equal @user.notifications.count, 3
+
+    create_views(BadgeFamousQuestion.badge_award_thresholds[0] + 1, BadgeFamousQuestion.badge_award_thresholds[1], @post)
+    assert_equal @user.notifications.count, 4
+
+    create_views(BadgeFamousQuestion.badge_award_thresholds[1] + 1, BadgeFamousQuestion.badge_award_thresholds[2], @post)
+    assert_equal @user.notifications.count, 5
+
+    create_views(BadgeFamousQuestion.badge_award_thresholds[2] + 1, BadgeFamousQuestion.badge_award_thresholds[2] + 2, @post)
+    assert_equal @user.notifications.count, 5
+  end
+
   def create_views(start, stop, post)
     (start..stop).each do |i|
       unique_user = users("user_test_#{i}")
diff --git a/test/models/badges/badge_great_answer_test.rb b/test/models/badges/badge_great_answer_test.rb
index 02fd0bb74..a3a1e8f48 100644
--- a/test/models/badges/badge_great_answer_test.rb
+++ b/test/models/badges/badge_great_answer_test.rb
@@ -51,4 +51,36 @@ class BadgeGreatAnswerTest < ActionController::TestCase
     assert_equal expected_count, post.upvote_count
     assert UserBadge.exists?(user: author, badge_id: BadgeGreatAnswer.badge_id)
   end
+
+  test 'user receives only one notification per badge tier' do
+    post = posts(:answer_one)
+    author = post.user
+    old_votes = post.upvote_count
+
+    assert_equal author.notifications.count, 2
+
+    # Create a badge (bronze, initially)
+    post.score = BadgeGreatAnswer.badge_award_thresholds[0] + 0.01
+    create_vote_and_check_badge(post, author, users(:standard_user), old_votes + 1)
+    assert_equal author.notifications.count, 3
+
+    # Create a vote and upgrade the badge to silver
+    post.score = BadgeGreatAnswer.badge_award_thresholds[1] + 0.01
+    create_vote_and_check_badge(post, author, users(:editor), old_votes + 2)
+    assert_equal author.notifications.count, 4
+
+    # Create a vote and upgrade the badge to gold
+    post.score = BadgeGreatAnswer.badge_award_thresholds[2] + 0.01
+    create_vote_and_check_badge(post, author, users(:closer), old_votes + 3)
+    assert_equal author.notifications.count, 5
+
+    # Create additional votes, for checking notifications
+    post.score = BadgeGreatAnswer.badge_award_thresholds[2] + 0.01
+    create_vote_and_check_badge(post, author, users(:standard_user), old_votes + 4)
+    post.score = BadgeGreatAnswer.badge_award_thresholds[2] + 0.01
+    create_vote_and_check_badge(post, author, users(:editor), old_votes + 5)
+    post.score = BadgeGreatAnswer.badge_award_thresholds[2] + 0.01
+    create_vote_and_check_badge(post, author, users(:closer), old_votes + 6)
+    assert_equal author.notifications.count, 5
+  end
 end
diff --git a/test/models/badges/badge_great_question_test.rb b/test/models/badges/badge_great_question_test.rb
index fe8bdbecd..7f4b10b28 100644
--- a/test/models/badges/badge_great_question_test.rb
+++ b/test/models/badges/badge_great_question_test.rb
@@ -29,6 +29,22 @@ class BadgeGreatQuestionTest < ActionController::TestCase
     assert_equal @user.user_badges.where(badge_id: @badge.id, badge_source: @post).count, 0
   end
 
+  test 'user receives only one notification per badge tier' do
+    assert_equal @user.notifications.count, 2
+
+    create_votes(1, BRONZE_VOTES, @post)
+    assert_equal @user.notifications.count, 3
+
+    create_votes(BRONZE_VOTES + 1, SILVER_VOTES, @post)
+    assert_equal @user.notifications.count, 4
+
+    create_votes(SILVER_VOTES + 1, GOLD_VOTES, @post)
+    assert_equal @user.notifications.count, 5
+
+    create_votes(GOLD_VOTES + 1, GOLD_VOTES + 2, @post)
+    assert_equal @user.notifications.count, 5
+  end
+
   def create_votes(start, stop, post)
     (start..stop).each do |i|
       unique_user = users("user_test_#{i}")
-- 
GitLab