From 2dc554c5dcc8e4be2ac7627d1d7e54832b4f280f Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Thu, 19 Feb 2026 09:28:57 -0500 Subject: [PATCH 01/15] 783: refresh cmnd --- .../codebloom/jda/command/JDASlashCommand.java | 3 ++- .../codebloom/jda/command/JDASlashCommandHandler.java | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommand.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommand.java index 2aa17e788..49763096f 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommand.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommand.java @@ -10,7 +10,8 @@ @AllArgsConstructor @ToString public enum JDASlashCommand { - LEADERBOARD("leaderboard", "Shows the current weekly leaderboard"); + LEADERBOARD("leaderboard", "Shows the current weekly leaderboard"), + REFRESH("refresh", "Refresh submissions manually"); private final String command; private final String description; diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index 5535c7a8f..160030072 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -27,10 +27,21 @@ public JDASlashCommandHandler( public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { switch (JDASlashCommand.fromCommand(event.getName())) { case LEADERBOARD -> handleLeaderboardSlashCommand(event); + case REFRESH -> handleRefreshSlashCommand(event); default -> throw new IllegalArgumentException("Unknown slash command: " + event.getName()); } } + private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { + if (event.getGuild() == null) { + event.reply("This command can only be used in a server.") + .setEphemeral(true) + .queue(); + return; + } + throw new UnsupportedOperationException("Unimplemented method 'handleRefreshSlashCommand'"); + } + private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { if (event.getGuild() == null) { From 19f2ec5c9cf3b5d3d69532eddb0492e112e12af9 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Fri, 20 Feb 2026 15:07:23 -0500 Subject: [PATCH 02/15] 783: refresh command that checks place of club and global leaderboard --- .../common/components/DiscordClubManager.java | 51 +++++++++++++++- .../jda/command/JDASlashCommandHandler.java | 60 ++++++++++++------- 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index 50fb91215..92b2fb8da 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -17,6 +17,8 @@ import org.patinanetwork.codebloom.common.db.repos.leaderboard.LeaderboardRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterGenerator; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterOptions; +import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; +import org.patinanetwork.codebloom.common.page.Indexed; import org.patinanetwork.codebloom.common.time.StandardizedLocalDateTime; import org.patinanetwork.codebloom.common.url.ServerUrlUtils; import org.patinanetwork.codebloom.common.utils.leaderboard.LeaderboardUtils; @@ -31,17 +33,20 @@ public class DiscordClubManager { private final JDAClient jdaClient; private final LeaderboardRepository leaderboardRepository; private final DiscordClubRepository discordClubRepository; + private final UserRepository userRepository; private final ServerUrlUtils serverUrlUtils; public DiscordClubManager( final ServerUrlUtils serverUrlUtils, final JDAClient jdaClient, final LeaderboardRepository leaderboardRepository, - final DiscordClubRepository discordClubRepository) { + final DiscordClubRepository discordClubRepository, + final UserRepository userRepository) { this.serverUrlUtils = serverUrlUtils; this.jdaClient = jdaClient; this.leaderboardRepository = leaderboardRepository; this.discordClubRepository = discordClubRepository; + this.userRepository = userRepository; } private static final String[] MEDAL_EMOJIS = {"🥇", "🥈", "🥉"}; @@ -270,6 +275,50 @@ public MessageCreateData buildLeaderboardMessageForClub(String guildId, boolean return MessageCreateData.fromEmbeds(embed); } + public MessageCreateData buildRefreshMessageForClub(String guildId, String discordId) { + + DiscordClub club = + discordClubRepository.getDiscordClubByGuildId(guildId).orElseThrow(); + Leaderboard currentLeaderboard = leaderboardRepository.getRecentLeaderboardMetadata(); + + LeaderboardFilterOptions options = LeaderboardFilterGenerator.builderWithTag(club.getTag()) + .page(1) + .pageSize(5) + .build(); + + String userId = userRepository.getUserByDiscordId(discordId).getId(); + UserWithScore user = + userRepository.getUserWithScoreByIdAndLeaderboardId(userId, currentLeaderboard.getId(), null); + + int score = user.getTotalScore(); + Indexed globalIndex = + leaderboardRepository.getGlobalRankedUserById(currentLeaderboard.getId(), userId); + Indexed clubIndex = + leaderboardRepository.getFilteredRankedUserById(currentLeaderboard.getId(), userId, options); + int globalPlace = globalIndex.getIndex() + 1; + int clubPlace = clubIndex.getIndex() + 1; + + String description = + String.format(""" + Your score is %d + + You are currently number %d on the global leaderboard: %s + + You are currently number %d for %s + """, score, globalPlace, currentLeaderboard.getName(), clubPlace, club.getName()); + + MessageEmbed embed = new EmbedBuilder() + .setTitle("%s Leaderboard Update for %s".formatted(currentLeaderboard.getName(), club.getName())) + .setDescription(description) + .setFooter( + "Codebloom - LeetCode Leaderboard for %s".formatted(club.getName()), + "%s/favicon.ico".formatted(serverUrlUtils.getUrl())) + .setColor(new Color(69, 129, 103)) + .build(); + + return MessageCreateData.fromEmbeds(embed); + } + public boolean sendTestEmbedMessageToClub(DiscordClub club) { try { String description = """ diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index 160030072..5268e0947 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -32,25 +32,13 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { } } - private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { - if (event.getGuild() == null) { - event.reply("This command can only be used in a server.") - .setEphemeral(true) - .queue(); - return; - } - throw new UnsupportedOperationException("Unimplemented method 'handleRefreshSlashCommand'"); - } - - private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { - - if (event.getGuild() == null) { - event.reply("This command can only be used in a server.") - .setEphemeral(true) - .queue(); - return; - } - + /** + * In memory rate limiter. + * + * @param event Slash command event + * @param time Time to reuse + */ + private void handleRedis(SlashCommandInteractionEvent event, final long time) { String guildId = event.getGuild().getId(); if (simpleRedis.containsKey(guildId)) { @@ -58,8 +46,8 @@ private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { long timeNow = System.currentTimeMillis(); long difference = (timeNow - timeThen) / 1000; - if (difference < 600) { - long remainingTime = 600 - difference; + if (difference < time) { + long remainingTime = time - difference; long minutes = remainingTime / 60; long seconds = remainingTime % 60; @@ -74,9 +62,35 @@ private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { } simpleRedis.put(guildId, System.currentTimeMillis()); + } - MessageCreateData message = discordClubManager.buildLeaderboardMessageForClub( - event.getGuild().getId(), false); + private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { + if (event.getGuild() == null) { + event.reply("This command can only be used in a server.") + .setEphemeral(true) + .queue(); + return; + } + handleRedis(event, 5 * 60); + + String serverId = event.getGuild().getId(); + String userId = event.getUser().getId(); + + MessageCreateData message = discordClubManager.buildRefreshMessageForClub(serverId, userId); + + event.reply(message).queue(); + } + + private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { + if (event.getGuild() == null) { + event.reply("This command can only be used in a server.") + .setEphemeral(true) + .queue(); + return; + } + handleRedis(event, 10 * 60); + String serverId = event.getGuild().getId(); + MessageCreateData message = discordClubManager.buildLeaderboardMessageForClub(serverId, false); event.reply(message).queue(); } From 3df62145aa9e2e38c30281729c41c725080b947d Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Sun, 22 Feb 2026 18:34:25 -0500 Subject: [PATCH 03/15] 783: add refresh logic and change index logic --- .../common/components/DiscordClubManager.java | 20 ++++++++++++++++--- .../jda/command/JDASlashCommandHandler.java | 4 ++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index 92b2fb8da..c2ba23a27 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -18,7 +18,10 @@ import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterGenerator; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterOptions; import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; +import org.patinanetwork.codebloom.common.leetcode.LeetcodeClient; +import org.patinanetwork.codebloom.common.leetcode.models.LeetcodeSubmission; import org.patinanetwork.codebloom.common.page.Indexed; +import org.patinanetwork.codebloom.common.submissions.SubmissionsHandler; import org.patinanetwork.codebloom.common.time.StandardizedLocalDateTime; import org.patinanetwork.codebloom.common.url.ServerUrlUtils; import org.patinanetwork.codebloom.common.utils.leaderboard.LeaderboardUtils; @@ -35,18 +38,24 @@ public class DiscordClubManager { private final DiscordClubRepository discordClubRepository; private final UserRepository userRepository; private final ServerUrlUtils serverUrlUtils; + private final LeetcodeClient leetcodeClient; + private final SubmissionsHandler submissionsHandler; public DiscordClubManager( final ServerUrlUtils serverUrlUtils, final JDAClient jdaClient, final LeaderboardRepository leaderboardRepository, final DiscordClubRepository discordClubRepository, - final UserRepository userRepository) { + final UserRepository userRepository, + final LeetcodeClient leetcodeClient, + final SubmissionsHandler submissionsHandler) { this.serverUrlUtils = serverUrlUtils; this.jdaClient = jdaClient; this.leaderboardRepository = leaderboardRepository; this.discordClubRepository = discordClubRepository; this.userRepository = userRepository; + this.leetcodeClient = leetcodeClient; + this.submissionsHandler = submissionsHandler; } private static final String[] MEDAL_EMOJIS = {"🥇", "🥈", "🥉"}; @@ -290,13 +299,18 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco UserWithScore user = userRepository.getUserWithScoreByIdAndLeaderboardId(userId, currentLeaderboard.getId(), null); + List leetcodeSubmissions = + leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername(), 20); + + submissionsHandler.handleSubmissions(leetcodeSubmissions, user); + int score = user.getTotalScore(); Indexed globalIndex = leaderboardRepository.getGlobalRankedUserById(currentLeaderboard.getId(), userId); Indexed clubIndex = leaderboardRepository.getFilteredRankedUserById(currentLeaderboard.getId(), userId, options); - int globalPlace = globalIndex.getIndex() + 1; - int clubPlace = clubIndex.getIndex() + 1; + int globalPlace = globalIndex.getIndex(); + int clubPlace = clubIndex.getIndex(); String description = String.format(""" diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index 5268e0947..8f5dc8048 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -73,10 +73,10 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { } handleRedis(event, 5 * 60); - String serverId = event.getGuild().getId(); + String guildId = event.getGuild().getId(); String userId = event.getUser().getId(); - MessageCreateData message = discordClubManager.buildRefreshMessageForClub(serverId, userId); + MessageCreateData message = discordClubManager.buildRefreshMessageForClub(guildId, userId); event.reply(message).queue(); } From 51d35af7aab686b8882b71ae2895c57e63a8634e Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Mon, 23 Feb 2026 15:28:56 -0500 Subject: [PATCH 04/15] 783: fixed inconsistent description and logic handling --- .../common/components/DiscordClubManager.java | 17 ++++++++++------- .../jda/command/JDASlashCommandHandler.java | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index c2ba23a27..c16f4a436 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -12,12 +12,14 @@ import org.patinanetwork.codebloom.common.db.models.discord.DiscordClub; import org.patinanetwork.codebloom.common.db.models.discord.DiscordClubMetadata; import org.patinanetwork.codebloom.common.db.models.leaderboard.Leaderboard; +import org.patinanetwork.codebloom.common.db.models.user.User; import org.patinanetwork.codebloom.common.db.models.user.UserWithScore; import org.patinanetwork.codebloom.common.db.repos.discord.club.DiscordClubRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.LeaderboardRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterGenerator; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterOptions; import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; +import org.patinanetwork.codebloom.common.db.repos.user.options.UserFilterOptions; import org.patinanetwork.codebloom.common.leetcode.LeetcodeClient; import org.patinanetwork.codebloom.common.leetcode.models.LeetcodeSubmission; import org.patinanetwork.codebloom.common.page.Indexed; @@ -295,16 +297,17 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco .pageSize(5) .build(); - String userId = userRepository.getUserByDiscordId(discordId).getId(); - UserWithScore user = - userRepository.getUserWithScoreByIdAndLeaderboardId(userId, currentLeaderboard.getId(), null); + User user = userRepository.getUserByDiscordId(discordId); + String userId = user.getId(); List leetcodeSubmissions = leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername(), 20); submissionsHandler.handleSubmissions(leetcodeSubmissions, user); - int score = user.getTotalScore(); + UserWithScore scoredUser = userRepository.getUserWithScoreByIdAndLeaderboardId(userId, currentLeaderboard.getId(), UserFilterOptions.DEFAULT); + + int score = scoredUser.getTotalScore(); Indexed globalIndex = leaderboardRepository.getGlobalRankedUserById(currentLeaderboard.getId(), userId); Indexed clubIndex = @@ -314,15 +317,15 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco String description = String.format(""" - Your score is %d + After refreshing your submissions, you currently have %s points. You are currently number %d on the global leaderboard: %s - You are currently number %d for %s + You are currently number %d on the club leaderboard: %s """, score, globalPlace, currentLeaderboard.getName(), clubPlace, club.getName()); MessageEmbed embed = new EmbedBuilder() - .setTitle("%s Leaderboard Update for %s".formatted(currentLeaderboard.getName(), club.getName())) + .setTitle("Submissions Refreshed") .setDescription(description) .setFooter( "Codebloom - LeetCode Leaderboard for %s".formatted(club.getName()), diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index 8f5dc8048..e64536ca3 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -78,7 +78,7 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { MessageCreateData message = discordClubManager.buildRefreshMessageForClub(guildId, userId); - event.reply(message).queue(); + event.reply(message).setEphemeral(true).queue(); } private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { @@ -92,6 +92,6 @@ private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { String serverId = event.getGuild().getId(); MessageCreateData message = discordClubManager.buildLeaderboardMessageForClub(serverId, false); - event.reply(message).queue(); + event.reply(message).setEphemeral(true).queue(); } } From 119083b5093d135ef9094a4e971e44e52ca23445 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Mon, 23 Feb 2026 15:33:10 -0500 Subject: [PATCH 05/15] 783: spotless fix --- .../codebloom/common/components/DiscordClubManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index c16f4a436..ded173f6b 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -305,7 +305,8 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco submissionsHandler.handleSubmissions(leetcodeSubmissions, user); - UserWithScore scoredUser = userRepository.getUserWithScoreByIdAndLeaderboardId(userId, currentLeaderboard.getId(), UserFilterOptions.DEFAULT); + UserWithScore scoredUser = userRepository.getUserWithScoreByIdAndLeaderboardId( + userId, currentLeaderboard.getId(), UserFilterOptions.DEFAULT); int score = scoredUser.getTotalScore(); Indexed globalIndex = From d62c4520edef25d600f91909cd5317cfcbdece83 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Mon, 23 Feb 2026 23:59:14 -0500 Subject: [PATCH 06/15] 783: fixed tests --- .../common/components/DiscordClubManager.java | 2 +- .../components/DiscordClubManagerTest.java | 16 +- .../jda/JDASlashCommandHandlerTest.java | 186 +++++++++++++++++- 3 files changed, 198 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index ded173f6b..965902a8e 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -320,7 +320,7 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco String.format(""" After refreshing your submissions, you currently have %s points. - You are currently number %d on the global leaderboard: %s + You are currently number %d on the global leaderboard: `%s` You are currently number %d on the club leaderboard: %s """, score, globalPlace, currentLeaderboard.getName(), clubPlace, club.getName()); diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java index d639a5d90..a02062109 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java @@ -26,6 +26,9 @@ import org.patinanetwork.codebloom.common.db.repos.discord.club.DiscordClubRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.LeaderboardRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterOptions; +import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; +import org.patinanetwork.codebloom.common.leetcode.LeetcodeClient; +import org.patinanetwork.codebloom.common.submissions.SubmissionsHandler; import org.patinanetwork.codebloom.common.url.ServerUrlUtils; import org.patinanetwork.codebloom.jda.client.JDAClient; import org.patinanetwork.codebloom.jda.client.options.EmbeddedImagesMessageOptions; @@ -39,6 +42,9 @@ public class DiscordClubManagerTest { private DiscordClubRepository discordClubRepository = mock(DiscordClubRepository.class); private PlaywrightClient playwrightClient = mock(PlaywrightClient.class); private ServerUrlUtils serverUrlUtils = mock(ServerUrlUtils.class); + private UserRepository userRepository = mock(UserRepository.class); + private LeetcodeClient leetcodeClient = mock(LeetcodeClient.class); + private SubmissionsHandler submissionsHandler = mock(SubmissionsHandler.class); private DiscordClubManager discordClubManager; @@ -46,8 +52,14 @@ public class DiscordClubManagerTest { @BeforeEach void setUp() { - discordClubManager = - new DiscordClubManager(serverUrlUtils, jdaClient, leaderboardRepository, discordClubRepository); + discordClubManager = new DiscordClubManager( + serverUrlUtils, + jdaClient, + leaderboardRepository, + discordClubRepository, + userRepository, + leetcodeClient, + submissionsHandler); logWatcher = new ListAppender<>(); logWatcher.start(); diff --git a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java index 77c5afb90..008948a26 100644 --- a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java @@ -1,5 +1,6 @@ package org.patinanetwork.codebloom.jda; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -7,6 +8,7 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import net.dv8tion.jda.api.utils.messages.MessageCreateData; @@ -77,19 +79,22 @@ void testOnSlashCommandInteractionSuccess() { .thenReturn(message); when(event.reply(eq(message))).thenReturn(replyAction); + when(replyAction.setEphemeral(true)).thenReturn(replyAction); handler.onSlashCommandInteraction(event); verify(simpleRedis).put(eq("guild-123"), anyLong()); verify(discordClubManager).buildLeaderboardMessageForClub("guild-123", false); verify(event).reply(message); + verify(replyAction).setEphemeral(true); verify(replyAction).queue(); } @Test void testOnSlashCommandInteractionCooldown() { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); - ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); + ReplyCallbackAction cooldownReplyAction = mock(ReplyCallbackAction.class); + ReplyCallbackAction leaderboardReplyAction = mock(ReplyCallbackAction.class); Guild guild = mock(Guild.class); when(event.getName()).thenReturn("leaderboard"); @@ -99,16 +104,191 @@ void testOnSlashCommandInteractionCooldown() { when(simpleRedis.containsKey("guild-123")).thenReturn(true); when(simpleRedis.get("guild-123")).thenReturn(System.currentTimeMillis()); - when(event.replyEmbeds(any(MessageEmbed.class))).thenReturn(replyAction); + // handleRedis sends the cooldown embed + when(event.replyEmbeds(any(MessageEmbed.class))).thenReturn(cooldownReplyAction); + when(cooldownReplyAction.setEphemeral(true)).thenReturn(cooldownReplyAction); + + // handleRedis is void, so handleLeaderboardSlashCommand continues after it + MessageCreateData message = mock(MessageCreateData.class); + when(discordClubManager.buildLeaderboardMessageForClub("guild-123", false)) + .thenReturn(message); + when(event.reply(eq(message))).thenReturn(leaderboardReplyAction); + when(leaderboardReplyAction.setEphemeral(true)).thenReturn(leaderboardReplyAction); + + handler.onSlashCommandInteraction(event); + + verify(event).replyEmbeds(any(MessageEmbed.class)); + verify(cooldownReplyAction).setEphemeral(true); + verify(cooldownReplyAction).queue(); + + // handleRedis returns before put, but execution continues in the caller + verify(simpleRedis, never()).put(eq("guild-123"), anyLong()); + verify(discordClubManager).buildLeaderboardMessageForClub("guild-123", false); + verify(event).reply(message); + verify(leaderboardReplyAction).setEphemeral(true); + verify(leaderboardReplyAction).queue(); + } + + @Test + void testLeaderboardCooldownExpired() { + SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); + ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); + Guild guild = mock(Guild.class); + + when(event.getName()).thenReturn("leaderboard"); + when(event.getGuild()).thenReturn(guild); + when(guild.getId()).thenReturn("guild-123"); + + when(simpleRedis.containsKey("guild-123")).thenReturn(true); + // Timestamp from 11 minutes ago — cooldown (10 min) has expired + when(simpleRedis.get("guild-123")).thenReturn(System.currentTimeMillis() - 11 * 60 * 1000); + + MessageCreateData message = mock(MessageCreateData.class); + when(discordClubManager.buildLeaderboardMessageForClub("guild-123", false)) + .thenReturn(message); + when(event.reply(eq(message))).thenReturn(replyAction); + when(replyAction.setEphemeral(true)).thenReturn(replyAction); + + handler.onSlashCommandInteraction(event); + + verify(simpleRedis).put(eq("guild-123"), anyLong()); + verify(event, never()).replyEmbeds(any(MessageEmbed.class)); + verify(discordClubManager).buildLeaderboardMessageForClub("guild-123", false); + verify(event).reply(message); + verify(replyAction).setEphemeral(true); + verify(replyAction).queue(); + } + + @Test + void testRefreshNoGuild() { + SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); + ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); + + when(event.getName()).thenReturn("refresh"); + when(event.getGuild()).thenReturn(null); + + when(event.reply("This command can only be used in a server.")).thenReturn(replyAction); when(replyAction.setEphemeral(true)).thenReturn(replyAction); handler.onSlashCommandInteraction(event); + verify(event).reply("This command can only be used in a server."); + verify(replyAction).setEphemeral(true); + verify(replyAction).queue(); + verifyNoInteractions(discordClubManager); + } + + @Test + void testRefreshSuccess() { + SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); + ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); + Guild guild = mock(Guild.class); + User user = mock(User.class); + + when(event.getName()).thenReturn("refresh"); + when(event.getGuild()).thenReturn(guild); + when(event.getUser()).thenReturn(user); + when(guild.getId()).thenReturn("guild-456"); + when(user.getId()).thenReturn("user-789"); + + when(simpleRedis.containsKey("guild-456")).thenReturn(false); + + MessageCreateData message = mock(MessageCreateData.class); + when(discordClubManager.buildRefreshMessageForClub("guild-456", "user-789")) + .thenReturn(message); + when(event.reply(eq(message))).thenReturn(replyAction); + when(replyAction.setEphemeral(true)).thenReturn(replyAction); + + handler.onSlashCommandInteraction(event); + + verify(simpleRedis).put(eq("guild-456"), anyLong()); + verify(discordClubManager).buildRefreshMessageForClub("guild-456", "user-789"); + verify(event).reply(message); + verify(replyAction).setEphemeral(true); + verify(replyAction).queue(); + } + + @Test + void testRefreshCooldown() { + SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); + ReplyCallbackAction cooldownReplyAction = mock(ReplyCallbackAction.class); + ReplyCallbackAction refreshReplyAction = mock(ReplyCallbackAction.class); + Guild guild = mock(Guild.class); + User user = mock(User.class); + + when(event.getName()).thenReturn("refresh"); + when(event.getGuild()).thenReturn(guild); + when(event.getUser()).thenReturn(user); + when(guild.getId()).thenReturn("guild-456"); + when(user.getId()).thenReturn("user-789"); + + when(simpleRedis.containsKey("guild-456")).thenReturn(true); + when(simpleRedis.get("guild-456")).thenReturn(System.currentTimeMillis()); + + // handleRedis sends cooldown embed + when(event.replyEmbeds(any(MessageEmbed.class))).thenReturn(cooldownReplyAction); + when(cooldownReplyAction.setEphemeral(true)).thenReturn(cooldownReplyAction); + + // handleRedis is void, so handleRefreshSlashCommand continues + MessageCreateData message = mock(MessageCreateData.class); + when(discordClubManager.buildRefreshMessageForClub("guild-456", "user-789")) + .thenReturn(message); + when(event.reply(eq(message))).thenReturn(refreshReplyAction); + when(refreshReplyAction.setEphemeral(true)).thenReturn(refreshReplyAction); + + handler.onSlashCommandInteraction(event); + verify(event).replyEmbeds(any(MessageEmbed.class)); + verify(cooldownReplyAction).setEphemeral(true); + verify(cooldownReplyAction).queue(); + + verify(simpleRedis, never()).put(eq("guild-456"), anyLong()); + verify(discordClubManager).buildRefreshMessageForClub("guild-456", "user-789"); + verify(event).reply(message); + verify(refreshReplyAction).setEphemeral(true); + verify(refreshReplyAction).queue(); + } + + @Test + void testRefreshCooldownExpired() { + SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); + ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); + Guild guild = mock(Guild.class); + User user = mock(User.class); + + when(event.getName()).thenReturn("refresh"); + when(event.getGuild()).thenReturn(guild); + when(event.getUser()).thenReturn(user); + when(guild.getId()).thenReturn("guild-456"); + when(user.getId()).thenReturn("user-789"); + + when(simpleRedis.containsKey("guild-456")).thenReturn(true); + // Timestamp from 6 minutes ago — cooldown (5 min) has expired + when(simpleRedis.get("guild-456")).thenReturn(System.currentTimeMillis() - 6 * 60 * 1000); + + MessageCreateData message = mock(MessageCreateData.class); + when(discordClubManager.buildRefreshMessageForClub("guild-456", "user-789")) + .thenReturn(message); + when(event.reply(eq(message))).thenReturn(replyAction); + when(replyAction.setEphemeral(true)).thenReturn(replyAction); + + handler.onSlashCommandInteraction(event); + + verify(simpleRedis).put(eq("guild-456"), anyLong()); + verify(event, never()).replyEmbeds(any(MessageEmbed.class)); + verify(discordClubManager).buildRefreshMessageForClub("guild-456", "user-789"); + verify(event).reply(message); verify(replyAction).setEphemeral(true); verify(replyAction).queue(); + } + + @Test + void testUnknownSlashCommandThrowsException() { + SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); + when(event.getName()).thenReturn("unknown-command"); + + assertThrows(IllegalArgumentException.class, () -> handler.onSlashCommandInteraction(event)); verifyNoInteractions(discordClubManager); - verify(simpleRedis, never()).put(eq("guild-123"), anyLong()); // because you return before put } } From 6c33eb913691d7e3345464fe2bb2131db05c067d Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Tue, 24 Feb 2026 23:56:16 -0500 Subject: [PATCH 07/15] 783: testing suite fix --- .../common/components/DiscordClubManager.java | 13 +++++++ .../jda/command/JDASlashCommandHandler.java | 13 ++++--- .../jda/JDASlashCommandHandlerTest.java | 35 +++---------------- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index 965902a8e..a2f9878db 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -300,6 +300,19 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco User user = userRepository.getUserByDiscordId(discordId); String userId = user.getId(); + if (user.getLeetcodeUsername() == null) { + String description = String.format(""" + Your Discord Account is not linked to a LeetCode username + """); + + MessageEmbed errorMsg = new EmbedBuilder() + .setTitle("Cannot refresh submissions") + .setDescription(description) + .setColor(new Color(255, 0, 0)) + .build(); + return MessageCreateData.fromEmbeds(errorMsg); + } + List leetcodeSubmissions = leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername(), 20); diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index e64536ca3..4bc795218 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -38,7 +38,7 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { * @param event Slash command event * @param time Time to reuse */ - private void handleRedis(SlashCommandInteractionEvent event, final long time) { + private boolean handleRedis(SlashCommandInteractionEvent event, final long time) { String guildId = event.getGuild().getId(); if (simpleRedis.containsKey(guildId)) { @@ -57,11 +57,12 @@ private void handleRedis(SlashCommandInteractionEvent event, final long time) { event.replyEmbeds(embed.build()).setEphemeral(true).queue(); - return; + return true; } } simpleRedis.put(guildId, System.currentTimeMillis()); + return false; } private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { @@ -71,7 +72,9 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { .queue(); return; } - handleRedis(event, 5 * 60); + if (handleRedis(event, 5 * 60)) { + return; + } String guildId = event.getGuild().getId(); String userId = event.getUser().getId(); @@ -88,7 +91,9 @@ private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { .queue(); return; } - handleRedis(event, 10 * 60); + if (handleRedis(event, 10 * 60)) { + return; + } String serverId = event.getGuild().getId(); MessageCreateData message = discordClubManager.buildLeaderboardMessageForClub(serverId, false); diff --git a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java index 008948a26..922122399 100644 --- a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java @@ -94,7 +94,6 @@ void testOnSlashCommandInteractionSuccess() { void testOnSlashCommandInteractionCooldown() { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); ReplyCallbackAction cooldownReplyAction = mock(ReplyCallbackAction.class); - ReplyCallbackAction leaderboardReplyAction = mock(ReplyCallbackAction.class); Guild guild = mock(Guild.class); when(event.getName()).thenReturn("leaderboard"); @@ -104,29 +103,18 @@ void testOnSlashCommandInteractionCooldown() { when(simpleRedis.containsKey("guild-123")).thenReturn(true); when(simpleRedis.get("guild-123")).thenReturn(System.currentTimeMillis()); - // handleRedis sends the cooldown embed when(event.replyEmbeds(any(MessageEmbed.class))).thenReturn(cooldownReplyAction); when(cooldownReplyAction.setEphemeral(true)).thenReturn(cooldownReplyAction); - // handleRedis is void, so handleLeaderboardSlashCommand continues after it - MessageCreateData message = mock(MessageCreateData.class); - when(discordClubManager.buildLeaderboardMessageForClub("guild-123", false)) - .thenReturn(message); - when(event.reply(eq(message))).thenReturn(leaderboardReplyAction); - when(leaderboardReplyAction.setEphemeral(true)).thenReturn(leaderboardReplyAction); - handler.onSlashCommandInteraction(event); verify(event).replyEmbeds(any(MessageEmbed.class)); verify(cooldownReplyAction).setEphemeral(true); verify(cooldownReplyAction).queue(); - // handleRedis returns before put, but execution continues in the caller verify(simpleRedis, never()).put(eq("guild-123"), anyLong()); - verify(discordClubManager).buildLeaderboardMessageForClub("guild-123", false); - verify(event).reply(message); - verify(leaderboardReplyAction).setEphemeral(true); - verify(leaderboardReplyAction).queue(); + verifyNoInteractions(discordClubManager); + verify(event, never()).reply(any(MessageCreateData.class)); } @Test @@ -212,30 +200,18 @@ void testRefreshSuccess() { void testRefreshCooldown() { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); ReplyCallbackAction cooldownReplyAction = mock(ReplyCallbackAction.class); - ReplyCallbackAction refreshReplyAction = mock(ReplyCallbackAction.class); Guild guild = mock(Guild.class); - User user = mock(User.class); when(event.getName()).thenReturn("refresh"); when(event.getGuild()).thenReturn(guild); - when(event.getUser()).thenReturn(user); when(guild.getId()).thenReturn("guild-456"); - when(user.getId()).thenReturn("user-789"); when(simpleRedis.containsKey("guild-456")).thenReturn(true); when(simpleRedis.get("guild-456")).thenReturn(System.currentTimeMillis()); - // handleRedis sends cooldown embed when(event.replyEmbeds(any(MessageEmbed.class))).thenReturn(cooldownReplyAction); when(cooldownReplyAction.setEphemeral(true)).thenReturn(cooldownReplyAction); - // handleRedis is void, so handleRefreshSlashCommand continues - MessageCreateData message = mock(MessageCreateData.class); - when(discordClubManager.buildRefreshMessageForClub("guild-456", "user-789")) - .thenReturn(message); - when(event.reply(eq(message))).thenReturn(refreshReplyAction); - when(refreshReplyAction.setEphemeral(true)).thenReturn(refreshReplyAction); - handler.onSlashCommandInteraction(event); verify(event).replyEmbeds(any(MessageEmbed.class)); @@ -243,10 +219,8 @@ void testRefreshCooldown() { verify(cooldownReplyAction).queue(); verify(simpleRedis, never()).put(eq("guild-456"), anyLong()); - verify(discordClubManager).buildRefreshMessageForClub("guild-456", "user-789"); - verify(event).reply(message); - verify(refreshReplyAction).setEphemeral(true); - verify(refreshReplyAction).queue(); + verifyNoInteractions(discordClubManager); + verify(event, never()).reply(any(MessageCreateData.class)); } @Test @@ -263,7 +237,6 @@ void testRefreshCooldownExpired() { when(user.getId()).thenReturn("user-789"); when(simpleRedis.containsKey("guild-456")).thenReturn(true); - // Timestamp from 6 minutes ago — cooldown (5 min) has expired when(simpleRedis.get("guild-456")).thenReturn(System.currentTimeMillis() - 6 * 60 * 1000); MessageCreateData message = mock(MessageCreateData.class); From 460a7ca79dcbf3b3cba83df5f2f47a3fe7399395 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Tue, 24 Feb 2026 23:57:14 -0500 Subject: [PATCH 08/15] 783: testing fix --- .../patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java index 922122399..36c066cb8 100644 --- a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java @@ -128,7 +128,6 @@ void testLeaderboardCooldownExpired() { when(guild.getId()).thenReturn("guild-123"); when(simpleRedis.containsKey("guild-123")).thenReturn(true); - // Timestamp from 11 minutes ago — cooldown (10 min) has expired when(simpleRedis.get("guild-123")).thenReturn(System.currentTimeMillis() - 11 * 60 * 1000); MessageCreateData message = mock(MessageCreateData.class); From 83cc9031dc38579a629dcf397bad72dded62e3b8 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Sun, 1 Mar 2026 03:55:53 -0500 Subject: [PATCH 09/15] 783: fix to when refresh takes too long --- .../common/components/DiscordClubManager.java | 41 +++++++++++------ .../jda/command/JDASlashCommandHandler.java | 46 +++++++++++-------- 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index a2f9878db..d20e80acd 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -298,25 +298,40 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco .build(); User user = userRepository.getUserByDiscordId(discordId); + + if (user == null) { + MessageEmbed errorMsg = new EmbedBuilder() + .setTitle("Cannot refresh submissions") + .setDescription( + "Your Discord Account is not linked to a CodeBloom user. Please link your account first.") + .setColor(new Color(255, 0, 0)) + .build(); + return MessageCreateData.fromEmbeds(errorMsg); + } + String userId = user.getId(); if (user.getLeetcodeUsername() == null) { - String description = String.format(""" - Your Discord Account is not linked to a LeetCode username - """); - MessageEmbed errorMsg = new EmbedBuilder() .setTitle("Cannot refresh submissions") - .setDescription(description) + .setDescription("Your Discord Account is not linked to a LeetCode username") .setColor(new Color(255, 0, 0)) .build(); return MessageCreateData.fromEmbeds(errorMsg); } + try { + List leetcodeSubmissions = + leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername(), 20); - List leetcodeSubmissions = - leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername(), 20); - - submissionsHandler.handleSubmissions(leetcodeSubmissions, user); + submissionsHandler.handleSubmissions(leetcodeSubmissions, user, false); + } catch (Exception e) { + MessageEmbed errorMsg = new EmbedBuilder() + .setTitle("Cannot refresh submissions") + .setDescription("Failed to fetch or process submissions from LeetCode. Please try again later.") + .setColor(new Color(255, 0, 0)) + .build(); + return MessageCreateData.fromEmbeds(errorMsg); + } UserWithScore scoredUser = userRepository.getUserWithScoreByIdAndLeaderboardId( userId, currentLeaderboard.getId(), UserFilterOptions.DEFAULT); @@ -326,16 +341,16 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco leaderboardRepository.getGlobalRankedUserById(currentLeaderboard.getId(), userId); Indexed clubIndex = leaderboardRepository.getFilteredRankedUserById(currentLeaderboard.getId(), userId, options); - int globalPlace = globalIndex.getIndex(); - int clubPlace = clubIndex.getIndex(); + String globalPlace = String.valueOf(globalIndex.getIndex()); + String clubPlace = String.valueOf(clubIndex.getIndex()); String description = String.format(""" After refreshing your submissions, you currently have %s points. - You are currently number %d on the global leaderboard: `%s` + You are currently number %s on the global leaderboard: `%s` - You are currently number %d on the club leaderboard: %s + You are currently number %s on the club leaderboard: %s """, score, globalPlace, currentLeaderboard.getName(), clubPlace, club.getName()); MessageEmbed embed = new EmbedBuilder() diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index 4bc795218..698eda309 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -1,6 +1,8 @@ package org.patinanetwork.codebloom.jda.command; import java.awt.Color; +import java.util.concurrent.CompletableFuture; + import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; @@ -40,9 +42,13 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { */ private boolean handleRedis(SlashCommandInteractionEvent event, final long time) { String guildId = event.getGuild().getId(); + String command = event.getName(); + String key = "refresh".equals(command) + ? guildId + ":" + command + ":" + event.getUser().getId() + : guildId + ":" + command; - if (simpleRedis.containsKey(guildId)) { - long timeThen = simpleRedis.get(guildId); + if (simpleRedis.containsKey(key)) { + long timeThen = simpleRedis.get(key); long timeNow = System.currentTimeMillis(); long difference = (timeNow - timeThen) / 1000; @@ -61,28 +67,32 @@ private boolean handleRedis(SlashCommandInteractionEvent event, final long time) } } - simpleRedis.put(guildId, System.currentTimeMillis()); + simpleRedis.put(key, System.currentTimeMillis()); return false; } private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { - if (event.getGuild() == null) { - event.reply("This command can only be used in a server.") - .setEphemeral(true) - .queue(); - return; - } - if (handleRedis(event, 5 * 60)) { - return; - } - - String guildId = event.getGuild().getId(); - String userId = event.getUser().getId(); - - MessageCreateData message = discordClubManager.buildRefreshMessageForClub(guildId, userId); + event.deferReply().setEphemeral(true).queue(); - event.reply(message).setEphemeral(true).queue(); + if (event.getGuild() == null) { + event.getHook().sendMessage("This command can only be used in a server.").queue(); + return; + } + if (handleRedis(event, 5 * 60)) { + return; } + String guildId = event.getGuild().getId(); + String userId = event.getUser().getId(); + CompletableFuture.runAsync(() -> { + try { + MessageCreateData message = discordClubManager.buildRefreshMessageForClub(guildId, userId); + event.getHook().sendMessage(message).queue(); + } catch (Exception e) { + event.getHook().sendMessage("An error occurred while trying to refresh.").queue(); + e.printStackTrace(); + } + }); +} private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { if (event.getGuild() == null) { From faf6df2216529abf88d3f71058fbceb394f002f2 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Sun, 1 Mar 2026 05:01:29 -0500 Subject: [PATCH 10/15] 783: fixed bot taking too long --- .../jda/command/JDASlashCommandHandler.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index 698eda309..256c1a3d1 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -2,7 +2,6 @@ import java.awt.Color; import java.util.concurrent.CompletableFuture; - import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; @@ -72,27 +71,31 @@ private boolean handleRedis(SlashCommandInteractionEvent event, final long time) } private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { - event.deferReply().setEphemeral(true).queue(); + event.deferReply().setEphemeral(true).queue(); - if (event.getGuild() == null) { - event.getHook().sendMessage("This command can only be used in a server.").queue(); - return; - } - if (handleRedis(event, 5 * 60)) { - return; - } - String guildId = event.getGuild().getId(); - String userId = event.getUser().getId(); - CompletableFuture.runAsync(() -> { - try { - MessageCreateData message = discordClubManager.buildRefreshMessageForClub(guildId, userId); - event.getHook().sendMessage(message).queue(); - } catch (Exception e) { - event.getHook().sendMessage("An error occurred while trying to refresh.").queue(); - e.printStackTrace(); + if (event.getGuild() == null) { + event.getHook() + .sendMessage("This command can only be used in a server.") + .queue(); + return; } - }); -} + if (handleRedis(event, 5 * 60)) { + return; + } + String guildId = event.getGuild().getId(); + String userId = event.getUser().getId(); + CompletableFuture.runAsync(() -> { + try { + MessageCreateData message = discordClubManager.buildRefreshMessageForClub(guildId, userId); + event.getHook().sendMessage(message).queue(); + } catch (Exception e) { + event.getHook() + .sendMessage("An error occurred while trying to refresh.") + .queue(); + e.printStackTrace(); + } + }); + } private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { if (event.getGuild() == null) { From 7fda8268e123a976e7e3a272a7b80c1319f50de4 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Sun, 1 Mar 2026 23:37:59 -0500 Subject: [PATCH 11/15] 783: rank fix --- .../codebloom/common/components/DiscordClubManager.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index d20e80acd..04f3bc6f6 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -292,10 +292,8 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco discordClubRepository.getDiscordClubByGuildId(guildId).orElseThrow(); Leaderboard currentLeaderboard = leaderboardRepository.getRecentLeaderboardMetadata(); - LeaderboardFilterOptions options = LeaderboardFilterGenerator.builderWithTag(club.getTag()) - .page(1) - .pageSize(5) - .build(); + LeaderboardFilterOptions options = + LeaderboardFilterGenerator.builderWithTag(club.getTag()).build(); User user = userRepository.getUserByDiscordId(discordId); From 26d0f6efe45284b88d50d7a8d68d978c04ef835a Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Mon, 2 Mar 2026 14:43:45 -0500 Subject: [PATCH 12/15] 783: fixes to use leaderboardManager and properly throw DiscordClubExceptions --- .../components/DiscordClubException.java | 18 +++++ .../common/components/DiscordClubManager.java | 57 +++---------- .../common/components/LeaderboardManager.java | 46 ++++++++++- .../codebloom/common/page/Indexed.java | 1 + .../jda/command/JDASlashCommandHandler.java | 81 ++++++++++++++----- .../components/DiscordClubManagerTest.java | 4 +- .../components/LeaderboardManagerTest.java | 9 ++- .../jda/JDASlashCommandHandlerTest.java | 5 +- 8 files changed, 153 insertions(+), 68 deletions(-) create mode 100644 src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubException.java diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubException.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubException.java new file mode 100644 index 000000000..94c8228d9 --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubException.java @@ -0,0 +1,18 @@ +package org.patinanetwork.codebloom.common.components; + +import lombok.Getter; + +@Getter +public class DiscordClubException extends Exception { + private final String title; + private final String description; + + public DiscordClubException(Throwable t) { + this("Something went wrong!", t.getMessage()); + } + + public DiscordClubException(String title, String description) { + this.title = title; + this.description = description; + } +} diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index 04f3bc6f6..294871ec6 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -21,7 +21,6 @@ import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; import org.patinanetwork.codebloom.common.db.repos.user.options.UserFilterOptions; import org.patinanetwork.codebloom.common.leetcode.LeetcodeClient; -import org.patinanetwork.codebloom.common.leetcode.models.LeetcodeSubmission; import org.patinanetwork.codebloom.common.page.Indexed; import org.patinanetwork.codebloom.common.submissions.SubmissionsHandler; import org.patinanetwork.codebloom.common.time.StandardizedLocalDateTime; @@ -42,6 +41,7 @@ public class DiscordClubManager { private final ServerUrlUtils serverUrlUtils; private final LeetcodeClient leetcodeClient; private final SubmissionsHandler submissionsHandler; + private final LeaderboardManager leaderboardManager; public DiscordClubManager( final ServerUrlUtils serverUrlUtils, @@ -50,7 +50,8 @@ public DiscordClubManager( final DiscordClubRepository discordClubRepository, final UserRepository userRepository, final LeetcodeClient leetcodeClient, - final SubmissionsHandler submissionsHandler) { + final SubmissionsHandler submissionsHandler, + final LeaderboardManager leaderboardManager) { this.serverUrlUtils = serverUrlUtils; this.jdaClient = jdaClient; this.leaderboardRepository = leaderboardRepository; @@ -58,6 +59,7 @@ public DiscordClubManager( this.userRepository = userRepository; this.leetcodeClient = leetcodeClient; this.submissionsHandler = submissionsHandler; + this.leaderboardManager = leaderboardManager; } private static final String[] MEDAL_EMOJIS = {"🥇", "🥈", "🥉"}; @@ -286,51 +288,20 @@ public MessageCreateData buildLeaderboardMessageForClub(String guildId, boolean return MessageCreateData.fromEmbeds(embed); } - public MessageCreateData buildRefreshMessageForClub(String guildId, String discordId) { + public MessageCreateData buildRefreshMessageForClub(String guildId, String discordId) throws DiscordClubException { - DiscordClub club = - discordClubRepository.getDiscordClubByGuildId(guildId).orElseThrow(); + DiscordClub club = discordClubRepository + .getDiscordClubByGuildId(guildId) + .orElseThrow(() -> new DiscordClubException("Club does not exist", "This club does not exist!")); + + User user = leaderboardManager.refreshUserSubmissions(discordId); Leaderboard currentLeaderboard = leaderboardRepository.getRecentLeaderboardMetadata(); LeaderboardFilterOptions options = LeaderboardFilterGenerator.builderWithTag(club.getTag()).build(); - User user = userRepository.getUserByDiscordId(discordId); - - if (user == null) { - MessageEmbed errorMsg = new EmbedBuilder() - .setTitle("Cannot refresh submissions") - .setDescription( - "Your Discord Account is not linked to a CodeBloom user. Please link your account first.") - .setColor(new Color(255, 0, 0)) - .build(); - return MessageCreateData.fromEmbeds(errorMsg); - } - String userId = user.getId(); - if (user.getLeetcodeUsername() == null) { - MessageEmbed errorMsg = new EmbedBuilder() - .setTitle("Cannot refresh submissions") - .setDescription("Your Discord Account is not linked to a LeetCode username") - .setColor(new Color(255, 0, 0)) - .build(); - return MessageCreateData.fromEmbeds(errorMsg); - } - try { - List leetcodeSubmissions = - leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername(), 20); - - submissionsHandler.handleSubmissions(leetcodeSubmissions, user, false); - } catch (Exception e) { - MessageEmbed errorMsg = new EmbedBuilder() - .setTitle("Cannot refresh submissions") - .setDescription("Failed to fetch or process submissions from LeetCode. Please try again later.") - .setColor(new Color(255, 0, 0)) - .build(); - return MessageCreateData.fromEmbeds(errorMsg); - } - UserWithScore scoredUser = userRepository.getUserWithScoreByIdAndLeaderboardId( userId, currentLeaderboard.getId(), UserFilterOptions.DEFAULT); @@ -339,17 +310,15 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco leaderboardRepository.getGlobalRankedUserById(currentLeaderboard.getId(), userId); Indexed clubIndex = leaderboardRepository.getFilteredRankedUserById(currentLeaderboard.getId(), userId, options); - String globalPlace = String.valueOf(globalIndex.getIndex()); - String clubPlace = String.valueOf(clubIndex.getIndex()); - String description = - String.format(""" + String description = String.format( + """ After refreshing your submissions, you currently have %s points. You are currently number %s on the global leaderboard: `%s` You are currently number %s on the club leaderboard: %s - """, score, globalPlace, currentLeaderboard.getName(), clubPlace, club.getName()); + """, score, globalIndex.getIndex(), currentLeaderboard.getName(), clubIndex.getIndex(), club.getName()); MessageEmbed embed = new EmbedBuilder() .setTitle("Submissions Refreshed") diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java index 72ffa154d..15aeafd86 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java @@ -5,15 +5,20 @@ import org.patinanetwork.codebloom.common.db.models.achievements.Achievement; import org.patinanetwork.codebloom.common.db.models.achievements.AchievementPlaceEnum; import org.patinanetwork.codebloom.common.db.models.leaderboard.Leaderboard; +import org.patinanetwork.codebloom.common.db.models.user.User; import org.patinanetwork.codebloom.common.db.models.user.UserWithScore; import org.patinanetwork.codebloom.common.db.models.usertag.Tag; import org.patinanetwork.codebloom.common.db.repos.achievements.AchievementRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.LeaderboardRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterGenerator; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterOptions; +import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; import org.patinanetwork.codebloom.common.dto.user.UserWithScoreDto; +import org.patinanetwork.codebloom.common.leetcode.LeetcodeClient; +import org.patinanetwork.codebloom.common.leetcode.models.LeetcodeSubmission; import org.patinanetwork.codebloom.common.page.Indexed; import org.patinanetwork.codebloom.common.page.Page; +import org.patinanetwork.codebloom.common.submissions.SubmissionsHandler; import org.patinanetwork.codebloom.common.utils.leaderboard.LeaderboardUtils; import org.patinanetwork.codebloom.common.utils.pair.Pair; import org.springframework.stereotype.Component; @@ -24,11 +29,21 @@ public class LeaderboardManager { private final LeaderboardRepository leaderboardRepository; private final AchievementRepository achievementRepository; + private final UserRepository userRepository; + private final LeetcodeClient leetcodeClient; + private final SubmissionsHandler submissionsHandler; public LeaderboardManager( - final LeaderboardRepository leaderboardRepository, final AchievementRepository achievementRepository) { + final LeaderboardRepository leaderboardRepository, + final AchievementRepository achievementRepository, + final UserRepository userRepository, + final LeetcodeClient leetcodeClient, + final SubmissionsHandler submissionsHandler) { this.leaderboardRepository = leaderboardRepository; this.achievementRepository = achievementRepository; + this.userRepository = userRepository; + this.leetcodeClient = leetcodeClient; + this.submissionsHandler = submissionsHandler; } public static final int MIN_POSSIBLE_WINNERS = 0; @@ -151,4 +166,33 @@ public Page> getLeaderboardUsers( new Page<>(hasNextPage, indexedUserWithScoreDtos, totalPages, parsedPageSize); return createdPage; } + + public User refreshUserSubmissions(final String discordId) throws DiscordClubException { + User user = userRepository.getUserByDiscordId(discordId); + + if (user == null) { + throw new DiscordClubException( + "Cannot refresh submissions", + "Please link your account by [logging in to Codebloom](https://codebloom.patinanetwork.org/login) and complete onboarding."); + } + + if (user.getLeetcodeUsername() == null) { + throw new DiscordClubException( + "Cannot refresh submissions", "Your Discord Account is not linked to a LeetCode username."); + } + try { + log.info("Fetching recent LeetCode submissions for user: {}", user.getLeetcodeUsername()); + List leetcodeSubmissions = + leetcodeClient.findSubmissionsByUsername(user.getLeetcodeUsername(), 20); + + submissionsHandler.handleSubmissions(leetcodeSubmissions, user, true); + } catch (Exception e) { + log.error("Failed to fetch or process submissions for user {}", user.getLeetcodeUsername(), e); + throw new DiscordClubException( + "Cannot refresh submissions", + "Failed to fetch or process submissions from LeetCode. Please try again later."); + } + + return user; + } } diff --git a/src/main/java/org/patinanetwork/codebloom/common/page/Indexed.java b/src/main/java/org/patinanetwork/codebloom/common/page/Indexed.java index d8cc43a5c..2992a6156 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/page/Indexed.java +++ b/src/main/java/org/patinanetwork/codebloom/common/page/Indexed.java @@ -25,6 +25,7 @@ @EqualsAndHashCode public class Indexed { + /** 1 Indexed integer representing rank on leaderboard. */ @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private final int index; diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index 256c1a3d1..43c96538a 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -1,11 +1,18 @@ package org.patinanetwork.codebloom.jda.command; import java.awt.Color; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import net.dv8tion.jda.api.utils.messages.MessageEditData; +import org.patinanetwork.codebloom.common.components.DiscordClubException; import org.patinanetwork.codebloom.common.components.DiscordClubManager; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedis; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedisProvider; @@ -17,11 +24,13 @@ public class JDASlashCommandHandler extends ListenerAdapter { private final DiscordClubManager discordClubManager; private final SimpleRedis simpleRedis; + private final ExecutorService pool; public JDASlashCommandHandler( DiscordClubManager discordClubManager, final SimpleRedisProvider simpleRedisProvider) { this.discordClubManager = discordClubManager; this.simpleRedis = simpleRedisProvider.select(SimpleRedisSlot.JDA_COOLDOWN); + this.pool = Executors.newVirtualThreadPerTaskExecutor(); } @Override @@ -37,9 +46,9 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { * In memory rate limiter. * * @param event Slash command event - * @param time Time to reuse + * @param time Time to reuse in seconds */ - private boolean handleRedis(SlashCommandInteractionEvent event, final long time) { + private long handleRedis(SlashCommandInteractionEvent event, final long time) { String guildId = event.getGuild().getId(); String command = event.getName(); String key = "refresh".equals(command) @@ -53,21 +62,13 @@ private boolean handleRedis(SlashCommandInteractionEvent event, final long time) if (difference < time) { long remainingTime = time - difference; - long minutes = remainingTime / 60; - long seconds = remainingTime % 60; - EmbedBuilder embed = new EmbedBuilder() - .setTitle("Please try again in **" + minutes + " minutes and " + seconds + " seconds**.") - .setColor(new Color(69, 129, 103)); - - event.replyEmbeds(embed.build()).setEphemeral(true).queue(); - - return true; + return remainingTime; } } simpleRedis.put(key, System.currentTimeMillis()); - return false; + return 0; } private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { @@ -79,22 +80,55 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { .queue(); return; } - if (handleRedis(event, 5 * 60)) { + long remainingTime = handleRedis(event, 5 * 60); + long minutes = remainingTime / 60; + long seconds = remainingTime % 60; + + if (remainingTime != 0) { + String description = String.format(""" + Please wait %s minutes and %s seconds before refreshing! + """, minutes, seconds); + + MessageEmbed rateLimitEmbed = new EmbedBuilder() + .setTitle("⏳ You are refreshing too quickly!") + .setDescription(description) + .setColor(Color.ORANGE) + .build(); + + event.getHook().editOriginalEmbeds(rateLimitEmbed).queue(); return; } String guildId = event.getGuild().getId(); String userId = event.getUser().getId(); - CompletableFuture.runAsync(() -> { + var future = pool.submit(() -> { try { MessageCreateData message = discordClubManager.buildRefreshMessageForClub(guildId, userId); - event.getHook().sendMessage(message).queue(); - } catch (Exception e) { event.getHook() - .sendMessage("An error occurred while trying to refresh.") + .editOriginal(MessageEditData.fromCreateData(message)) .queue(); + } catch (DiscordClubException e) { + MessageEmbed message = new EmbedBuilder() + .setTitle(e.getTitle()) + .setDescription(e.getDescription()) + .setColor(Color.RED) + .build(); + event.getHook().editOriginalEmbeds(message).queue(); e.printStackTrace(); } }); + + try { + future.get(2500, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (TimeoutException | ExecutionException exception) { + event.getHook().editOriginal(""" + Hmm, the refresh operation is taking longer than expected :( + + We’ll keep trying behind the scenes. On completion, this message will update to reflect your points and rank. + """).queue(); + exception.printStackTrace(); + } } private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { @@ -104,7 +138,16 @@ private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { .queue(); return; } - if (handleRedis(event, 10 * 60)) { + long remainingTime = handleRedis(event, 10 * 60); + long minutes = remainingTime / 60; + long seconds = remainingTime % 60; + + String title = String.format(""" + Please try again in %s minutes and %s seconds. + """, minutes, seconds); + if (remainingTime != 0) { + EmbedBuilder embed = new EmbedBuilder().setTitle(title).setColor(new Color(69, 129, 103)); + event.replyEmbeds(embed.build()).setEphemeral(true).queue(); return; } String serverId = event.getGuild().getId(); diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java index a02062109..383d62b0a 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java @@ -45,6 +45,7 @@ public class DiscordClubManagerTest { private UserRepository userRepository = mock(UserRepository.class); private LeetcodeClient leetcodeClient = mock(LeetcodeClient.class); private SubmissionsHandler submissionsHandler = mock(SubmissionsHandler.class); + private LeaderboardManager leaderboardManager = mock(LeaderboardManager.class); private DiscordClubManager discordClubManager; @@ -59,7 +60,8 @@ void setUp() { discordClubRepository, userRepository, leetcodeClient, - submissionsHandler); + submissionsHandler, + leaderboardManager); logWatcher = new ListAppender<>(); logWatcher.start(); diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java index e5fd649a2..b5e5c588b 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/LeaderboardManagerTest.java @@ -29,7 +29,10 @@ import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterGenerator; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterGeneratorTest; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterOptions; +import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; +import org.patinanetwork.codebloom.common.leetcode.LeetcodeClient; import org.patinanetwork.codebloom.common.page.Indexed; +import org.patinanetwork.codebloom.common.submissions.SubmissionsHandler; import org.patinanetwork.codebloom.common.time.StandardizedLocalDateTime; public class LeaderboardManagerTest { @@ -39,11 +42,15 @@ public class LeaderboardManagerTest { private LeaderboardRepository leaderboardRepository = mock(LeaderboardRepository.class); private AchievementRepository achievementRepository = mock(AchievementRepository.class); + private UserRepository userRepository = mock(UserRepository.class); + private LeetcodeClient leetcodeClient = mock(LeetcodeClient.class); + private SubmissionsHandler submissionsHandler = mock(SubmissionsHandler.class); private final int validLeaderboardTags = LeaderboardFilterGeneratorTest.VALID_LEADERBOARD_TAGS.size(); public LeaderboardManagerTest() { - this.leaderboardManager = new LeaderboardManager(leaderboardRepository, achievementRepository); + this.leaderboardManager = new LeaderboardManager( + leaderboardRepository, achievementRepository, userRepository, leetcodeClient, submissionsHandler); this.faker = Faker.instance(); } diff --git a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java index 36c066cb8..1fabf6c96 100644 --- a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.patinanetwork.codebloom.common.components.DiscordClubException; import org.patinanetwork.codebloom.common.components.DiscordClubManager; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedis; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedisProvider; @@ -166,7 +167,7 @@ void testRefreshNoGuild() { } @Test - void testRefreshSuccess() { + void testRefreshSuccess() throws DiscordClubException { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); Guild guild = mock(Guild.class); @@ -223,7 +224,7 @@ void testRefreshCooldown() { } @Test - void testRefreshCooldownExpired() { + void testRefreshCooldownExpired() throws DiscordClubException { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); Guild guild = mock(Guild.class); From 119be025f4503b4860c17d5f7cbbd670fef1f46f Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Mon, 2 Mar 2026 15:39:50 -0500 Subject: [PATCH 13/15] 783: fixed test suite and use DTO --- .../common/components/DiscordClubManager.java | 37 +--- ...ception.java => LeaderboardException.java} | 6 +- .../common/components/LeaderboardManager.java | 8 +- .../common/dto/refresh/RefreshResultDto.java | 31 +++ .../codebloom/common/page/Indexed.java | 2 - .../jda/command/JDASlashCommandHandler.java | 50 +++-- .../components/DiscordClubManagerTest.java | 6 - .../jda/JDASlashCommandHandlerTest.java | 181 ++++++++++++------ 8 files changed, 207 insertions(+), 114 deletions(-) rename src/main/java/org/patinanetwork/codebloom/common/components/{DiscordClubException.java => LeaderboardException.java} (64%) create mode 100644 src/main/java/org/patinanetwork/codebloom/common/dto/refresh/RefreshResultDto.java diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index 294871ec6..2dc36ab86 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -20,9 +20,8 @@ import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterOptions; import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; import org.patinanetwork.codebloom.common.db.repos.user.options.UserFilterOptions; -import org.patinanetwork.codebloom.common.leetcode.LeetcodeClient; +import org.patinanetwork.codebloom.common.dto.refresh.RefreshResultDto; import org.patinanetwork.codebloom.common.page.Indexed; -import org.patinanetwork.codebloom.common.submissions.SubmissionsHandler; import org.patinanetwork.codebloom.common.time.StandardizedLocalDateTime; import org.patinanetwork.codebloom.common.url.ServerUrlUtils; import org.patinanetwork.codebloom.common.utils.leaderboard.LeaderboardUtils; @@ -39,8 +38,6 @@ public class DiscordClubManager { private final DiscordClubRepository discordClubRepository; private final UserRepository userRepository; private final ServerUrlUtils serverUrlUtils; - private final LeetcodeClient leetcodeClient; - private final SubmissionsHandler submissionsHandler; private final LeaderboardManager leaderboardManager; public DiscordClubManager( @@ -49,16 +46,12 @@ public DiscordClubManager( final LeaderboardRepository leaderboardRepository, final DiscordClubRepository discordClubRepository, final UserRepository userRepository, - final LeetcodeClient leetcodeClient, - final SubmissionsHandler submissionsHandler, final LeaderboardManager leaderboardManager) { this.serverUrlUtils = serverUrlUtils; this.jdaClient = jdaClient; this.leaderboardRepository = leaderboardRepository; this.discordClubRepository = discordClubRepository; this.userRepository = userRepository; - this.leetcodeClient = leetcodeClient; - this.submissionsHandler = submissionsHandler; this.leaderboardManager = leaderboardManager; } @@ -288,11 +281,11 @@ public MessageCreateData buildLeaderboardMessageForClub(String guildId, boolean return MessageCreateData.fromEmbeds(embed); } - public MessageCreateData buildRefreshMessageForClub(String guildId, String discordId) throws DiscordClubException { + public RefreshResultDto refreshSubmissions(String guildId, String discordId) throws LeaderboardException { DiscordClub club = discordClubRepository .getDiscordClubByGuildId(guildId) - .orElseThrow(() -> new DiscordClubException("Club does not exist", "This club does not exist!")); + .orElseThrow(() -> new LeaderboardException("Club does not exist", "This club does not exist!")); User user = leaderboardManager.refreshUserSubmissions(discordId); Leaderboard currentLeaderboard = leaderboardRepository.getRecentLeaderboardMetadata(); @@ -311,25 +304,13 @@ public MessageCreateData buildRefreshMessageForClub(String guildId, String disco Indexed clubIndex = leaderboardRepository.getFilteredRankedUserById(currentLeaderboard.getId(), userId, options); - String description = String.format( - """ - After refreshing your submissions, you currently have %s points. - - You are currently number %s on the global leaderboard: `%s` - - You are currently number %s on the club leaderboard: %s - """, score, globalIndex.getIndex(), currentLeaderboard.getName(), clubIndex.getIndex(), club.getName()); - - MessageEmbed embed = new EmbedBuilder() - .setTitle("Submissions Refreshed") - .setDescription(description) - .setFooter( - "Codebloom - LeetCode Leaderboard for %s".formatted(club.getName()), - "%s/favicon.ico".formatted(serverUrlUtils.getUrl())) - .setColor(new Color(69, 129, 103)) + return RefreshResultDto.builder() + .score(score) + .globalRank(globalIndex.getIndex()) + .clubRank(clubIndex.getIndex()) + .leaderboardName(currentLeaderboard.getName()) + .clubName(club.getName()) .build(); - - return MessageCreateData.fromEmbeds(embed); } public boolean sendTestEmbedMessageToClub(DiscordClub club) { diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubException.java b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardException.java similarity index 64% rename from src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubException.java rename to src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardException.java index 94c8228d9..3ecd5f612 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubException.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardException.java @@ -3,15 +3,15 @@ import lombok.Getter; @Getter -public class DiscordClubException extends Exception { +public class LeaderboardException extends Exception { private final String title; private final String description; - public DiscordClubException(Throwable t) { + public LeaderboardException(Throwable t) { this("Something went wrong!", t.getMessage()); } - public DiscordClubException(String title, String description) { + public LeaderboardException(String title, String description) { this.title = title; this.description = description; } diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java index 15aeafd86..adea950e0 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java @@ -167,17 +167,17 @@ public Page> getLeaderboardUsers( return createdPage; } - public User refreshUserSubmissions(final String discordId) throws DiscordClubException { + public User refreshUserSubmissions(final String discordId) throws LeaderboardException { User user = userRepository.getUserByDiscordId(discordId); if (user == null) { - throw new DiscordClubException( + throw new LeaderboardException( "Cannot refresh submissions", "Please link your account by [logging in to Codebloom](https://codebloom.patinanetwork.org/login) and complete onboarding."); } if (user.getLeetcodeUsername() == null) { - throw new DiscordClubException( + throw new LeaderboardException( "Cannot refresh submissions", "Your Discord Account is not linked to a LeetCode username."); } try { @@ -188,7 +188,7 @@ public User refreshUserSubmissions(final String discordId) throws DiscordClubExc submissionsHandler.handleSubmissions(leetcodeSubmissions, user, true); } catch (Exception e) { log.error("Failed to fetch or process submissions for user {}", user.getLeetcodeUsername(), e); - throw new DiscordClubException( + throw new LeaderboardException( "Cannot refresh submissions", "Failed to fetch or process submissions from LeetCode. Please try again later."); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/dto/refresh/RefreshResultDto.java b/src/main/java/org/patinanetwork/codebloom/common/dto/refresh/RefreshResultDto.java new file mode 100644 index 000000000..c08d6d401 --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/common/dto/refresh/RefreshResultDto.java @@ -0,0 +1,31 @@ +package org.patinanetwork.codebloom.common.dto.refresh; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import lombok.extern.jackson.Jacksonized; + +@Getter +@Builder +@Jacksonized +@ToString +@EqualsAndHashCode +public class RefreshResultDto { + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private int score; + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private int globalRank; + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private int clubRank; + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String leaderboardName; + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + private String clubName; +} diff --git a/src/main/java/org/patinanetwork/codebloom/common/page/Indexed.java b/src/main/java/org/patinanetwork/codebloom/common/page/Indexed.java index 2992a6156..ebb4929ca 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/page/Indexed.java +++ b/src/main/java/org/patinanetwork/codebloom/common/page/Indexed.java @@ -24,8 +24,6 @@ @ToString @EqualsAndHashCode public class Indexed { - - /** 1 Indexed integer representing rank on leaderboard. */ @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private final int index; diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index 43c96538a..d8d80471a 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -6,20 +6,22 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import lombok.extern.slf4j.Slf4j; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import net.dv8tion.jda.api.utils.messages.MessageEditData; -import org.patinanetwork.codebloom.common.components.DiscordClubException; import org.patinanetwork.codebloom.common.components.DiscordClubManager; +import org.patinanetwork.codebloom.common.components.LeaderboardException; +import org.patinanetwork.codebloom.common.dto.refresh.RefreshResultDto; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedis; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedisProvider; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedisSlot; import org.springframework.stereotype.Component; @Component +@Slf4j public class JDASlashCommandHandler extends ListenerAdapter { private final DiscordClubManager discordClubManager; @@ -89,31 +91,50 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { Please wait %s minutes and %s seconds before refreshing! """, minutes, seconds); - MessageEmbed rateLimitEmbed = new EmbedBuilder() + MessageEmbed message = new EmbedBuilder() .setTitle("⏳ You are refreshing too quickly!") .setDescription(description) .setColor(Color.ORANGE) .build(); - event.getHook().editOriginalEmbeds(rateLimitEmbed).queue(); + event.getHook().editOriginalEmbeds(message).queue(); return; } String guildId = event.getGuild().getId(); String userId = event.getUser().getId(); var future = pool.submit(() -> { try { - MessageCreateData message = discordClubManager.buildRefreshMessageForClub(guildId, userId); - event.getHook() - .editOriginal(MessageEditData.fromCreateData(message)) - .queue(); - } catch (DiscordClubException e) { + RefreshResultDto result = discordClubManager.refreshSubmissions(guildId, userId); + + String description = String.format( + """ + After refreshing your submissions, you currently have %s points. + + You are currently number %s on the global leaderboard: `%s` + + You are currently number %s on the club leaderboard: %s + """, + result.getScore(), + result.getGlobalRank(), + result.getLeaderboardName(), + result.getClubRank(), + result.getClubName()); + + MessageEmbed message = new EmbedBuilder() + .setTitle("Submissions Refreshed") + .setDescription(description) + .setColor(new Color(69, 129, 103)) + .build(); + + event.getHook().editOriginalEmbeds(message).queue(); + } catch (LeaderboardException e) { MessageEmbed message = new EmbedBuilder() .setTitle(e.getTitle()) .setDescription(e.getDescription()) .setColor(Color.RED) .build(); event.getHook().editOriginalEmbeds(message).queue(); - e.printStackTrace(); + log.error("Refresh for club failed ", e); } }); @@ -127,7 +148,7 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { We’ll keep trying behind the scenes. On completion, this message will update to reflect your points and rank. """).queue(); - exception.printStackTrace(); + log.info("Refresh for club taking longer than expected ", exception); } } @@ -146,8 +167,11 @@ private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { Please try again in %s minutes and %s seconds. """, minutes, seconds); if (remainingTime != 0) { - EmbedBuilder embed = new EmbedBuilder().setTitle(title).setColor(new Color(69, 129, 103)); - event.replyEmbeds(embed.build()).setEphemeral(true).queue(); + MessageEmbed message = new EmbedBuilder() + .setTitle(title) + .setColor(new Color(69, 129, 103)) + .build(); + event.replyEmbeds(message).setEphemeral(true).queue(); return; } String serverId = event.getGuild().getId(); diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java index 383d62b0a..e329436f0 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java @@ -27,8 +27,6 @@ import org.patinanetwork.codebloom.common.db.repos.leaderboard.LeaderboardRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterOptions; import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; -import org.patinanetwork.codebloom.common.leetcode.LeetcodeClient; -import org.patinanetwork.codebloom.common.submissions.SubmissionsHandler; import org.patinanetwork.codebloom.common.url.ServerUrlUtils; import org.patinanetwork.codebloom.jda.client.JDAClient; import org.patinanetwork.codebloom.jda.client.options.EmbeddedImagesMessageOptions; @@ -43,8 +41,6 @@ public class DiscordClubManagerTest { private PlaywrightClient playwrightClient = mock(PlaywrightClient.class); private ServerUrlUtils serverUrlUtils = mock(ServerUrlUtils.class); private UserRepository userRepository = mock(UserRepository.class); - private LeetcodeClient leetcodeClient = mock(LeetcodeClient.class); - private SubmissionsHandler submissionsHandler = mock(SubmissionsHandler.class); private LeaderboardManager leaderboardManager = mock(LeaderboardManager.class); private DiscordClubManager discordClubManager; @@ -59,8 +55,6 @@ void setUp() { leaderboardRepository, discordClubRepository, userRepository, - leetcodeClient, - submissionsHandler, leaderboardManager); logWatcher = new ListAppender<>(); diff --git a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java index 1fabf6c96..c14b243c9 100644 --- a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java @@ -7,9 +7,13 @@ import static org.mockito.Mockito.*; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.requests.restaction.WebhookMessageCreateAction; +import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import net.dv8tion.jda.api.utils.messages.MessageCreateData; import org.junit.jupiter.api.BeforeEach; @@ -17,8 +21,9 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.patinanetwork.codebloom.common.components.DiscordClubException; import org.patinanetwork.codebloom.common.components.DiscordClubManager; +import org.patinanetwork.codebloom.common.components.LeaderboardException; +import org.patinanetwork.codebloom.common.dto.refresh.RefreshResultDto; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedis; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedisProvider; import org.patinanetwork.codebloom.common.simpleredis.SimpleRedisSlot; @@ -45,7 +50,7 @@ void setUp() { } @Test - void testOnSlashCommandInteractionNoGuild() { + void testLeaderboardNoGuild() { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); @@ -64,7 +69,7 @@ void testOnSlashCommandInteractionNoGuild() { } @Test - void testOnSlashCommandInteractionSuccess() { + void testLeaderboardSuccess() { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); Guild guild = mock(Guild.class); @@ -73,7 +78,7 @@ void testOnSlashCommandInteractionSuccess() { when(event.getGuild()).thenReturn(guild); when(guild.getId()).thenReturn("guild-123"); - when(simpleRedis.containsKey("guild-123")).thenReturn(false); + when(simpleRedis.containsKey("guild-123:leaderboard")).thenReturn(false); MessageCreateData message = mock(MessageCreateData.class); when(discordClubManager.buildLeaderboardMessageForClub("guild-123", false)) @@ -84,7 +89,7 @@ void testOnSlashCommandInteractionSuccess() { handler.onSlashCommandInteraction(event); - verify(simpleRedis).put(eq("guild-123"), anyLong()); + verify(simpleRedis).put(eq("guild-123:leaderboard"), anyLong()); verify(discordClubManager).buildLeaderboardMessageForClub("guild-123", false); verify(event).reply(message); verify(replyAction).setEphemeral(true); @@ -92,7 +97,7 @@ void testOnSlashCommandInteractionSuccess() { } @Test - void testOnSlashCommandInteractionCooldown() { + void testLeaderboardCooldown() { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); ReplyCallbackAction cooldownReplyAction = mock(ReplyCallbackAction.class); Guild guild = mock(Guild.class); @@ -101,8 +106,8 @@ void testOnSlashCommandInteractionCooldown() { when(event.getGuild()).thenReturn(guild); when(guild.getId()).thenReturn("guild-123"); - when(simpleRedis.containsKey("guild-123")).thenReturn(true); - when(simpleRedis.get("guild-123")).thenReturn(System.currentTimeMillis()); + when(simpleRedis.containsKey("guild-123:leaderboard")).thenReturn(true); + when(simpleRedis.get("guild-123:leaderboard")).thenReturn(System.currentTimeMillis()); when(event.replyEmbeds(any(MessageEmbed.class))).thenReturn(cooldownReplyAction); when(cooldownReplyAction.setEphemeral(true)).thenReturn(cooldownReplyAction); @@ -113,7 +118,7 @@ void testOnSlashCommandInteractionCooldown() { verify(cooldownReplyAction).setEphemeral(true); verify(cooldownReplyAction).queue(); - verify(simpleRedis, never()).put(eq("guild-123"), anyLong()); + verify(simpleRedis, never()).put(eq("guild-123:leaderboard"), anyLong()); verifyNoInteractions(discordClubManager); verify(event, never()).reply(any(MessageCreateData.class)); } @@ -128,8 +133,8 @@ void testLeaderboardCooldownExpired() { when(event.getGuild()).thenReturn(guild); when(guild.getId()).thenReturn("guild-123"); - when(simpleRedis.containsKey("guild-123")).thenReturn(true); - when(simpleRedis.get("guild-123")).thenReturn(System.currentTimeMillis() - 11 * 60 * 1000); + when(simpleRedis.containsKey("guild-123:leaderboard")).thenReturn(true); + when(simpleRedis.get("guild-123:leaderboard")).thenReturn(System.currentTimeMillis() - 11 * 60 * 1000); MessageCreateData message = mock(MessageCreateData.class); when(discordClubManager.buildLeaderboardMessageForClub("guild-123", false)) @@ -139,7 +144,7 @@ void testLeaderboardCooldownExpired() { handler.onSlashCommandInteraction(event); - verify(simpleRedis).put(eq("guild-123"), anyLong()); + verify(simpleRedis).put(eq("guild-123:leaderboard"), anyLong()); verify(event, never()).replyEmbeds(any(MessageEmbed.class)); verify(discordClubManager).buildLeaderboardMessageForClub("guild-123", false); verify(event).reply(message); @@ -150,109 +155,169 @@ void testLeaderboardCooldownExpired() { @Test void testRefreshNoGuild() { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); - ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); + ReplyCallbackAction deferAction = mock(ReplyCallbackAction.class); + InteractionHook hook = mock(InteractionHook.class); + WebhookMessageCreateAction sendAction = mock(WebhookMessageCreateAction.class); when(event.getName()).thenReturn("refresh"); + when(event.deferReply()).thenReturn(deferAction); + when(deferAction.setEphemeral(true)).thenReturn(deferAction); when(event.getGuild()).thenReturn(null); - - when(event.reply("This command can only be used in a server.")).thenReturn(replyAction); - when(replyAction.setEphemeral(true)).thenReturn(replyAction); + when(event.getHook()).thenReturn(hook); + when(hook.sendMessage("This command can only be used in a server.")).thenReturn(sendAction); handler.onSlashCommandInteraction(event); - verify(event).reply("This command can only be used in a server."); - verify(replyAction).setEphemeral(true); - verify(replyAction).queue(); + verify(event).deferReply(); + verify(deferAction).setEphemeral(true); + verify(deferAction).queue(); + verify(hook).sendMessage("This command can only be used in a server."); + verify(sendAction).queue(); verifyNoInteractions(discordClubManager); } @Test - void testRefreshSuccess() throws DiscordClubException { + void testRefreshSuccess() throws LeaderboardException { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); - ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); + ReplyCallbackAction deferAction = mock(ReplyCallbackAction.class); + InteractionHook hook = mock(InteractionHook.class); + WebhookMessageEditAction editAction = mock(WebhookMessageEditAction.class); Guild guild = mock(Guild.class); User user = mock(User.class); when(event.getName()).thenReturn("refresh"); + when(event.deferReply()).thenReturn(deferAction); + when(deferAction.setEphemeral(true)).thenReturn(deferAction); when(event.getGuild()).thenReturn(guild); when(event.getUser()).thenReturn(user); when(guild.getId()).thenReturn("guild-456"); when(user.getId()).thenReturn("user-789"); + when(event.getHook()).thenReturn(hook); - when(simpleRedis.containsKey("guild-456")).thenReturn(false); + when(simpleRedis.containsKey("guild-456:refresh:user-789")).thenReturn(false); - MessageCreateData message = mock(MessageCreateData.class); - when(discordClubManager.buildRefreshMessageForClub("guild-456", "user-789")) - .thenReturn(message); - when(event.reply(eq(message))).thenReturn(replyAction); - when(replyAction.setEphemeral(true)).thenReturn(replyAction); + RefreshResultDto result = RefreshResultDto.builder() + .score(42) + .globalRank(5) + .clubRank(2) + .leaderboardName("Week 10") + .clubName("TestClub") + .build(); + when(discordClubManager.refreshSubmissions("guild-456", "user-789")).thenReturn(result); + when(hook.editOriginalEmbeds(any(MessageEmbed.class))).thenReturn(editAction); handler.onSlashCommandInteraction(event); - verify(simpleRedis).put(eq("guild-456"), anyLong()); - verify(discordClubManager).buildRefreshMessageForClub("guild-456", "user-789"); - verify(event).reply(message); - verify(replyAction).setEphemeral(true); - verify(replyAction).queue(); + verify(event).deferReply(); + verify(deferAction).setEphemeral(true); + verify(deferAction).queue(); + verify(simpleRedis).put(eq("guild-456:refresh:user-789"), anyLong()); + verify(discordClubManager).refreshSubmissions("guild-456", "user-789"); + verify(hook).editOriginalEmbeds(any(MessageEmbed.class)); + verify(editAction).queue(); } @Test void testRefreshCooldown() { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); - ReplyCallbackAction cooldownReplyAction = mock(ReplyCallbackAction.class); + ReplyCallbackAction deferAction = mock(ReplyCallbackAction.class); + InteractionHook hook = mock(InteractionHook.class); + WebhookMessageEditAction editEmbedsAction = mock(WebhookMessageEditAction.class); Guild guild = mock(Guild.class); + User user = mock(User.class); when(event.getName()).thenReturn("refresh"); + when(event.deferReply()).thenReturn(deferAction); + when(deferAction.setEphemeral(true)).thenReturn(deferAction); when(event.getGuild()).thenReturn(guild); + when(event.getUser()).thenReturn(user); when(guild.getId()).thenReturn("guild-456"); + when(user.getId()).thenReturn("user-789"); + when(event.getHook()).thenReturn(hook); - when(simpleRedis.containsKey("guild-456")).thenReturn(true); - when(simpleRedis.get("guild-456")).thenReturn(System.currentTimeMillis()); + when(simpleRedis.containsKey("guild-456:refresh:user-789")).thenReturn(true); + when(simpleRedis.get("guild-456:refresh:user-789")).thenReturn(System.currentTimeMillis()); - when(event.replyEmbeds(any(MessageEmbed.class))).thenReturn(cooldownReplyAction); - when(cooldownReplyAction.setEphemeral(true)).thenReturn(cooldownReplyAction); + when(hook.editOriginalEmbeds(any(MessageEmbed.class))).thenReturn(editEmbedsAction); handler.onSlashCommandInteraction(event); - verify(event).replyEmbeds(any(MessageEmbed.class)); - verify(cooldownReplyAction).setEphemeral(true); - verify(cooldownReplyAction).queue(); - - verify(simpleRedis, never()).put(eq("guild-456"), anyLong()); + verify(event).deferReply(); + verify(hook).editOriginalEmbeds(any(MessageEmbed.class)); + verify(editEmbedsAction).queue(); + verify(simpleRedis, never()).put(eq("guild-456:refresh:user-789"), anyLong()); verifyNoInteractions(discordClubManager); - verify(event, never()).reply(any(MessageCreateData.class)); } @Test - void testRefreshCooldownExpired() throws DiscordClubException { + void testRefreshCooldownExpired() throws LeaderboardException { SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); - ReplyCallbackAction replyAction = mock(ReplyCallbackAction.class); + ReplyCallbackAction deferAction = mock(ReplyCallbackAction.class); + InteractionHook hook = mock(InteractionHook.class); + WebhookMessageEditAction editAction = mock(WebhookMessageEditAction.class); Guild guild = mock(Guild.class); User user = mock(User.class); when(event.getName()).thenReturn("refresh"); + when(event.deferReply()).thenReturn(deferAction); + when(deferAction.setEphemeral(true)).thenReturn(deferAction); when(event.getGuild()).thenReturn(guild); when(event.getUser()).thenReturn(user); when(guild.getId()).thenReturn("guild-456"); when(user.getId()).thenReturn("user-789"); + when(event.getHook()).thenReturn(hook); - when(simpleRedis.containsKey("guild-456")).thenReturn(true); - when(simpleRedis.get("guild-456")).thenReturn(System.currentTimeMillis() - 6 * 60 * 1000); + when(simpleRedis.containsKey("guild-456:refresh:user-789")).thenReturn(true); + when(simpleRedis.get("guild-456:refresh:user-789")).thenReturn(System.currentTimeMillis() - 6 * 60 * 1000); - MessageCreateData message = mock(MessageCreateData.class); - when(discordClubManager.buildRefreshMessageForClub("guild-456", "user-789")) - .thenReturn(message); - when(event.reply(eq(message))).thenReturn(replyAction); - when(replyAction.setEphemeral(true)).thenReturn(replyAction); + RefreshResultDto result = RefreshResultDto.builder() + .score(10) + .globalRank(3) + .clubRank(1) + .leaderboardName("Week 5") + .clubName("Club") + .build(); + when(discordClubManager.refreshSubmissions("guild-456", "user-789")).thenReturn(result); + when(hook.editOriginalEmbeds(any(MessageEmbed.class))).thenReturn(editAction); handler.onSlashCommandInteraction(event); - verify(simpleRedis).put(eq("guild-456"), anyLong()); - verify(event, never()).replyEmbeds(any(MessageEmbed.class)); - verify(discordClubManager).buildRefreshMessageForClub("guild-456", "user-789"); - verify(event).reply(message); - verify(replyAction).setEphemeral(true); - verify(replyAction).queue(); + verify(simpleRedis).put(eq("guild-456:refresh:user-789"), anyLong()); + verify(discordClubManager).refreshSubmissions("guild-456", "user-789"); + verify(hook).editOriginalEmbeds(any(MessageEmbed.class)); + verify(editAction).queue(); + } + + @Test + void testRefreshDiscordClubException() throws LeaderboardException { + SlashCommandInteractionEvent event = mock(SlashCommandInteractionEvent.class); + ReplyCallbackAction deferAction = mock(ReplyCallbackAction.class); + InteractionHook hook = mock(InteractionHook.class); + var editEmbedsAction = mock(WebhookMessageEditAction.class); + Guild guild = mock(Guild.class); + User user = mock(User.class); + + when(event.getName()).thenReturn("refresh"); + when(event.deferReply()).thenReturn(deferAction); + when(deferAction.setEphemeral(true)).thenReturn(deferAction); + when(event.getGuild()).thenReturn(guild); + when(event.getUser()).thenReturn(user); + when(guild.getId()).thenReturn("guild-456"); + when(user.getId()).thenReturn("user-789"); + when(event.getHook()).thenReturn(hook); + + when(simpleRedis.containsKey("guild-456:refresh:user-789")).thenReturn(false); + + when(discordClubManager.refreshSubmissions("guild-456", "user-789")) + .thenThrow(new LeaderboardException("Error Title", "Error Description")); + when(hook.editOriginalEmbeds(any(MessageEmbed.class))).thenReturn(editEmbedsAction); + + handler.onSlashCommandInteraction(event); + + verify(simpleRedis).put(eq("guild-456:refresh:user-789"), anyLong()); + verify(discordClubManager).refreshSubmissions("guild-456", "user-789"); + verify(hook).editOriginalEmbeds(any(MessageEmbed.class)); + verify(editEmbedsAction).queue(); } @Test From c7961d85ca5a73751d02276dad14f8209d153849 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Mon, 2 Mar 2026 16:17:12 -0500 Subject: [PATCH 14/15] 783: handle code coverage and reliability issues --- .../jda/command/JDASlashCommandHandler.java | 4 +- .../components/DiscordClubManagerTest.java | 150 ++++++++++++++++++ 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index d8d80471a..616b1b3b5 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -82,7 +82,7 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { .queue(); return; } - long remainingTime = handleRedis(event, 5 * 60); + long remainingTime = handleRedis(event, (long) 5 * 60); long minutes = remainingTime / 60; long seconds = remainingTime % 60; @@ -159,7 +159,7 @@ private void handleLeaderboardSlashCommand(SlashCommandInteractionEvent event) { .queue(); return; } - long remainingTime = handleRedis(event, 10 * 60); + long remainingTime = handleRedis(event, (long) 10 * 60); long minutes = remainingTime / 60; long seconds = remainingTime % 60; diff --git a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java index e329436f0..94c3db368 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/components/DiscordClubManagerTest.java @@ -21,12 +21,16 @@ import org.patinanetwork.codebloom.common.db.models.discord.DiscordClub; import org.patinanetwork.codebloom.common.db.models.discord.DiscordClubMetadata; import org.patinanetwork.codebloom.common.db.models.leaderboard.Leaderboard; +import org.patinanetwork.codebloom.common.db.models.user.User; import org.patinanetwork.codebloom.common.db.models.user.UserWithScore; import org.patinanetwork.codebloom.common.db.models.usertag.Tag; import org.patinanetwork.codebloom.common.db.repos.discord.club.DiscordClubRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.LeaderboardRepository; import org.patinanetwork.codebloom.common.db.repos.leaderboard.options.LeaderboardFilterOptions; import org.patinanetwork.codebloom.common.db.repos.user.UserRepository; +import org.patinanetwork.codebloom.common.db.repos.user.options.UserFilterOptions; +import org.patinanetwork.codebloom.common.dto.refresh.RefreshResultDto; +import org.patinanetwork.codebloom.common.page.Indexed; import org.patinanetwork.codebloom.common.url.ServerUrlUtils; import org.patinanetwork.codebloom.jda.client.JDAClient; import org.patinanetwork.codebloom.jda.client.options.EmbeddedImagesMessageOptions; @@ -379,6 +383,152 @@ void testSendTestEmbedMessageToClubSuccess() { assertTrue(description.contains("test message")); } + @Test + void testRefreshSubmissionsSuccess() throws LeaderboardException { + DiscordClub club = createMockDiscordClub("Test Club", Tag.Rpi); + when(discordClubRepository.getDiscordClubByGuildId("guild-123")).thenReturn(Optional.of(club)); + + User mockUser = mock(User.class); + when(mockUser.getId()).thenReturn("user-id-1"); + when(leaderboardManager.refreshUserSubmissions("discord-456")).thenReturn(mockUser); + + Leaderboard mockLeaderboard = mock(Leaderboard.class); + when(mockLeaderboard.getId()).thenReturn("leaderboard-id"); + when(mockLeaderboard.getName()).thenReturn("Week 10"); + when(leaderboardRepository.getRecentLeaderboardMetadata()).thenReturn(mockLeaderboard); + + UserWithScore scoredUser = mock(UserWithScore.class); + when(scoredUser.getTotalScore()).thenReturn(42); + when(userRepository.getUserWithScoreByIdAndLeaderboardId( + eq("user-id-1"), eq("leaderboard-id"), eq(UserFilterOptions.DEFAULT))) + .thenReturn(scoredUser); + + Indexed globalIndex = Indexed.of(scoredUser, 5); + Indexed clubIndex = Indexed.of(scoredUser, 2); + when(leaderboardRepository.getGlobalRankedUserById("leaderboard-id", "user-id-1")) + .thenReturn(globalIndex); + when(leaderboardRepository.getFilteredRankedUserById( + eq("leaderboard-id"), eq("user-id-1"), any(LeaderboardFilterOptions.class))) + .thenReturn(clubIndex); + + RefreshResultDto result = discordClubManager.refreshSubmissions("guild-123", "discord-456"); + + assertEquals(42, result.getScore()); + assertEquals(5, result.getGlobalRank()); + assertEquals(2, result.getClubRank()); + assertEquals("Week 10", result.getLeaderboardName()); + assertEquals("Test Club", result.getClubName()); + + verify(leaderboardManager).refreshUserSubmissions("discord-456"); + verify(discordClubRepository).getDiscordClubByGuildId("guild-123"); + } + + @Test + void testRefreshSubmissionsClubNotFound() { + when(discordClubRepository.getDiscordClubByGuildId("unknown-guild")).thenReturn(Optional.empty()); + + LeaderboardException exception = assertThrows( + LeaderboardException.class, + () -> discordClubManager.refreshSubmissions("unknown-guild", "discord-456")); + + assertEquals("Club does not exist", exception.getTitle()); + assertEquals("This club does not exist!", exception.getDescription()); + + verifyNoInteractions(leaderboardManager); + verifyNoInteractions(userRepository); + } + + @Test + void testRefreshSubmissionsUserRefreshThrowsLeaderboardException() throws LeaderboardException { + DiscordClub club = createMockDiscordClub("Test Club", Tag.Rpi); + when(discordClubRepository.getDiscordClubByGuildId("guild-123")).thenReturn(Optional.of(club)); + + when(leaderboardManager.refreshUserSubmissions("discord-456")) + .thenThrow(new LeaderboardException( + "Cannot refresh submissions", "Your Discord Account is not linked to a LeetCode username.")); + + LeaderboardException exception = assertThrows( + LeaderboardException.class, () -> discordClubManager.refreshSubmissions("guild-123", "discord-456")); + + assertEquals("Cannot refresh submissions", exception.getTitle()); + assertEquals("Your Discord Account is not linked to a LeetCode username.", exception.getDescription()); + + verify(leaderboardManager).refreshUserSubmissions("discord-456"); + verifyNoInteractions(userRepository); + } + + @Test + void testRefreshSubmissionsReturnsCorrectClubRankWithDifferentTags() throws LeaderboardException { + DiscordClub club = createMockDiscordClub("Baruch Club", Tag.Baruch); + when(discordClubRepository.getDiscordClubByGuildId("guild-789")).thenReturn(Optional.of(club)); + + User mockUser = mock(User.class); + when(mockUser.getId()).thenReturn("user-id-2"); + when(leaderboardManager.refreshUserSubmissions("discord-111")).thenReturn(mockUser); + + Leaderboard mockLeaderboard = mock(Leaderboard.class); + when(mockLeaderboard.getId()).thenReturn("lb-id"); + when(mockLeaderboard.getName()).thenReturn("Week 5"); + when(leaderboardRepository.getRecentLeaderboardMetadata()).thenReturn(mockLeaderboard); + + UserWithScore scoredUser = mock(UserWithScore.class); + when(scoredUser.getTotalScore()).thenReturn(100); + when(userRepository.getUserWithScoreByIdAndLeaderboardId( + eq("user-id-2"), eq("lb-id"), eq(UserFilterOptions.DEFAULT))) + .thenReturn(scoredUser); + + Indexed globalIndex = Indexed.of(scoredUser, 10); + Indexed clubIndex = Indexed.of(scoredUser, 1); + when(leaderboardRepository.getGlobalRankedUserById("lb-id", "user-id-2")) + .thenReturn(globalIndex); + when(leaderboardRepository.getFilteredRankedUserById( + eq("lb-id"), eq("user-id-2"), any(LeaderboardFilterOptions.class))) + .thenReturn(clubIndex); + + RefreshResultDto result = discordClubManager.refreshSubmissions("guild-789", "discord-111"); + + assertEquals(100, result.getScore()); + assertEquals(10, result.getGlobalRank()); + assertEquals(1, result.getClubRank()); + assertEquals("Week 5", result.getLeaderboardName()); + assertEquals("Baruch Club", result.getClubName()); + } + + @Test + void testRefreshSubmissionsWithZeroScore() throws LeaderboardException { + DiscordClub club = createMockDiscordClub("Test Club", Tag.Rpi); + when(discordClubRepository.getDiscordClubByGuildId("guild-123")).thenReturn(Optional.of(club)); + + User mockUser = mock(User.class); + when(mockUser.getId()).thenReturn("user-id-1"); + when(leaderboardManager.refreshUserSubmissions("discord-456")).thenReturn(mockUser); + + Leaderboard mockLeaderboard = mock(Leaderboard.class); + when(mockLeaderboard.getId()).thenReturn("leaderboard-id"); + when(mockLeaderboard.getName()).thenReturn("Week 1"); + when(leaderboardRepository.getRecentLeaderboardMetadata()).thenReturn(mockLeaderboard); + + UserWithScore scoredUser = mock(UserWithScore.class); + when(scoredUser.getTotalScore()).thenReturn(0); + when(userRepository.getUserWithScoreByIdAndLeaderboardId( + eq("user-id-1"), eq("leaderboard-id"), eq(UserFilterOptions.DEFAULT))) + .thenReturn(scoredUser); + + Indexed globalIndex = Indexed.of(scoredUser, 50); + Indexed clubIndex = Indexed.of(scoredUser, 20); + when(leaderboardRepository.getGlobalRankedUserById("leaderboard-id", "user-id-1")) + .thenReturn(globalIndex); + when(leaderboardRepository.getFilteredRankedUserById( + eq("leaderboard-id"), eq("user-id-1"), any(LeaderboardFilterOptions.class))) + .thenReturn(clubIndex); + + RefreshResultDto result = discordClubManager.refreshSubmissions("guild-123", "discord-456"); + + assertEquals(0, result.getScore()); + assertEquals(50, result.getGlobalRank()); + assertEquals(20, result.getClubRank()); + } + private DiscordClub createMockDiscordClub(final String name, final Tag tag) { DiscordClubMetadata metadata = mock(DiscordClubMetadata.class); when(metadata.getGuildId()).thenReturn(Optional.of("123456789")); From 8c78cbb2554e0986cc3bac92561be9406573296a Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Mon, 2 Mar 2026 23:39:35 -0500 Subject: [PATCH 15/15] 783: use messageEmbed instead of message --- .../common/components/DiscordClubManager.java | 1 - .../common/components/LeaderboardManager.java | 2 +- .../jda/command/JDASlashCommandHandler.java | 26 ++++++++++++------- .../jda/JDASlashCommandHandlerTest.java | 4 +-- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java index 2dc36ab86..13b8a1001 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/DiscordClubManager.java @@ -282,7 +282,6 @@ public MessageCreateData buildLeaderboardMessageForClub(String guildId, boolean } public RefreshResultDto refreshSubmissions(String guildId, String discordId) throws LeaderboardException { - DiscordClub club = discordClubRepository .getDiscordClubByGuildId(guildId) .orElseThrow(() -> new LeaderboardException("Club does not exist", "This club does not exist!")); diff --git a/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java index adea950e0..35012411f 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/components/LeaderboardManager.java @@ -173,7 +173,7 @@ public User refreshUserSubmissions(final String discordId) throws LeaderboardExc if (user == null) { throw new LeaderboardException( "Cannot refresh submissions", - "Please link your account by [logging in to Codebloom](https://codebloom.patinanetwork.org/login) and complete onboarding."); + "Please link your account by [logging in to Codebloom](https://codebloom.patinanetwork.org/login) and completing onboarding."); } if (user.getLeetcodeUsername() == null) { diff --git a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java index 616b1b3b5..e1a8b26da 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/command/JDASlashCommandHandler.java @@ -77,9 +77,11 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { event.deferReply().setEphemeral(true).queue(); if (event.getGuild() == null) { - event.getHook() - .sendMessage("This command can only be used in a server.") - .queue(); + MessageEmbed message = new EmbedBuilder() + .setTitle("This command can only be used in a server") + .setColor(Color.RED) + .build(); + event.getHook().sendMessageEmbeds(message).queue(); return; } long remainingTime = handleRedis(event, (long) 5 * 60); @@ -110,13 +112,11 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { """ After refreshing your submissions, you currently have %s points. - You are currently number %s on the global leaderboard: `%s` - - You are currently number %s on the club leaderboard: %s + For the leaderboard `%s`, you are currently #%s globally and #%s among all %s members. """, result.getScore(), - result.getGlobalRank(), result.getLeaderboardName(), + result.getGlobalRank(), result.getClubRank(), result.getClubName()); @@ -131,7 +131,7 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { MessageEmbed message = new EmbedBuilder() .setTitle(e.getTitle()) .setDescription(e.getDescription()) - .setColor(Color.RED) + .setColor(new Color(24, 162, 184)) .build(); event.getHook().editOriginalEmbeds(message).queue(); log.error("Refresh for club failed ", e); @@ -143,11 +143,17 @@ private void handleRefreshSlashCommand(SlashCommandInteractionEvent event) { } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (TimeoutException | ExecutionException exception) { - event.getHook().editOriginal(""" + String description = """ Hmm, the refresh operation is taking longer than expected :( We’ll keep trying behind the scenes. On completion, this message will update to reflect your points and rank. - """).queue(); + """; + MessageEmbed message = new EmbedBuilder() + .setTitle("Submissions Refreshed") + .setDescription(description) + .setColor(new Color(24, 162, 184)) + .build(); + event.getHook().editOriginalEmbeds(message).queue(); log.info("Refresh for club taking longer than expected ", exception); } } diff --git a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java index c14b243c9..844ce374e 100644 --- a/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/jda/JDASlashCommandHandlerTest.java @@ -164,14 +164,14 @@ void testRefreshNoGuild() { when(deferAction.setEphemeral(true)).thenReturn(deferAction); when(event.getGuild()).thenReturn(null); when(event.getHook()).thenReturn(hook); - when(hook.sendMessage("This command can only be used in a server.")).thenReturn(sendAction); + when(hook.sendMessageEmbeds(any(MessageEmbed.class))).thenReturn(sendAction); handler.onSlashCommandInteraction(event); verify(event).deferReply(); verify(deferAction).setEphemeral(true); verify(deferAction).queue(); - verify(hook).sendMessage("This command can only be used in a server."); + verify(hook).sendMessageEmbeds(any(MessageEmbed.class)); verify(sendAction).queue(); verifyNoInteractions(discordClubManager); }