|
| 1 | +package com.bencodez.votingplugin.tests.votestreak; |
| 2 | + |
| 3 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
| 4 | +import static org.junit.jupiter.api.Assertions.assertFalse; |
| 5 | +import static org.junit.jupiter.api.Assertions.assertNotNull; |
| 6 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
| 7 | +import static org.mockito.ArgumentMatchers.anyString; |
| 8 | +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; |
| 9 | +import static org.mockito.Mockito.doAnswer; |
| 10 | +import static org.mockito.Mockito.mock; |
| 11 | +import static org.mockito.Mockito.when; |
| 12 | + |
| 13 | +import java.time.LocalDateTime; |
| 14 | +import java.util.HashMap; |
| 15 | +import java.util.Map; |
| 16 | +import java.util.UUID; |
| 17 | +import java.util.logging.Level; |
| 18 | +import java.util.logging.Logger; |
| 19 | + |
| 20 | +import org.bukkit.configuration.ConfigurationSection; |
| 21 | +import org.bukkit.configuration.MemoryConfiguration; |
| 22 | +import org.junit.jupiter.api.BeforeEach; |
| 23 | +import org.junit.jupiter.api.Test; |
| 24 | + |
| 25 | +import com.bencodez.advancedcore.api.time.TimeChecker; |
| 26 | +import com.bencodez.votingplugin.VotingPluginMain; |
| 27 | +import com.bencodez.votingplugin.specialrewards.votestreak.VoteStreakDefinition; |
| 28 | +import com.bencodez.votingplugin.specialrewards.votestreak.VoteStreakHandler; |
| 29 | +import com.bencodez.votingplugin.specialrewards.votestreak.VoteStreakType; |
| 30 | +import com.bencodez.votingplugin.user.VotingPluginUser; |
| 31 | + |
| 32 | +class VoteStreakHandlerTest { |
| 33 | + |
| 34 | + private VotingPluginMain plugin; |
| 35 | + private TimeChecker timeChecker; |
| 36 | + private VoteStreakHandler handler; |
| 37 | + |
| 38 | + @BeforeEach |
| 39 | + void setup() { |
| 40 | + plugin = mock(VotingPluginMain.class, RETURNS_DEEP_STUBS); |
| 41 | + timeChecker = mock(TimeChecker.class); |
| 42 | + |
| 43 | + when(plugin.getTimeChecker()).thenReturn(timeChecker); |
| 44 | + when(timeChecker.getTime()).thenReturn(LocalDateTime.of(2026, 1, 10, 12, 0)); |
| 45 | + when(plugin.getLogger()).thenReturn(Logger.getLogger("VoteStreakHandlerTest")); |
| 46 | + |
| 47 | + when(plugin.getLogger()).thenReturn(silentLogger()); |
| 48 | + |
| 49 | + handler = new VoteStreakHandler(plugin); |
| 50 | + } |
| 51 | + |
| 52 | + private static VotingPluginUser mapBackedUser(UUID uuid, String name, Map<String, String> backing) { |
| 53 | + VotingPluginUser user = mock(VotingPluginUser.class); |
| 54 | + when(user.getJavaUUID()).thenReturn(uuid); |
| 55 | + when(user.getPlayerName()).thenReturn(name); |
| 56 | + |
| 57 | + when(user.getVoteStreakState(anyString())).thenAnswer(inv -> backing.get(inv.getArgument(0, String.class))); |
| 58 | + |
| 59 | + doAnswer(inv -> { |
| 60 | + backing.put(inv.getArgument(0, String.class), inv.getArgument(1, String.class)); |
| 61 | + return null; |
| 62 | + }).when(user).setVoteStreakState(anyString(), anyString()); |
| 63 | + |
| 64 | + return user; |
| 65 | + } |
| 66 | + |
| 67 | + |
| 68 | + private static Logger silentLogger() { |
| 69 | + Logger l = Logger.getLogger("silent-test-logger"); |
| 70 | + l.setUseParentHandlers(false); // <- key: stops ConsoleHandler printing |
| 71 | + l.setLevel(Level.OFF); // or Level.WARNING if you want warnings |
| 72 | + // also remove any handlers that might already be attached |
| 73 | + java.util.logging.Handler[] hs = l.getHandlers(); |
| 74 | + for (java.util.logging.Handler h : hs) { |
| 75 | + l.removeHandler(h); |
| 76 | + } |
| 77 | + return l; |
| 78 | + } |
| 79 | + |
| 80 | + |
| 81 | + private static MemoryConfiguration rootWithOneStreak(String id, String type, boolean enabled, int intervalAmount, |
| 82 | + int votesRequired, int allowMissedAmount, int allowMissedPeriod) { |
| 83 | + |
| 84 | + MemoryConfiguration root = new MemoryConfiguration(); |
| 85 | + |
| 86 | + ConfigurationSection def = root.createSection("VoteStreaks").createSection(id); |
| 87 | + def.set("Type", type); |
| 88 | + def.set("Enabled", enabled); |
| 89 | + |
| 90 | + ConfigurationSection req = def.createSection("Requirements"); |
| 91 | + req.set("Amount", intervalAmount); |
| 92 | + req.set("VotesRequired", votesRequired); |
| 93 | + |
| 94 | + def.set("AllowMissedAmount", allowMissedAmount); |
| 95 | + def.set("AllowMissedPeriod", allowMissedPeriod); |
| 96 | + |
| 97 | + return root; |
| 98 | + } |
| 99 | + |
| 100 | + /** |
| 101 | + * periodKey|streakCount|votesThisPeriod|countedThisPeriod|missWindowStartKey|missesUsed |
| 102 | + */ |
| 103 | + private static String[] parseState(String raw) { |
| 104 | + assertNotNull(raw); |
| 105 | + assertFalse(raw.isEmpty()); |
| 106 | + String[] p = raw.split("\\|", -1); |
| 107 | + assertTrue(p.length >= 6, "expected >= 6 fields, got " + p.length + " raw=" + raw); |
| 108 | + return p; |
| 109 | + } |
| 110 | + |
| 111 | + private void loadFromRoot(ConfigurationSection root) { |
| 112 | + // IMPORTANT: no plugin.getSpecialRewardsConfig() involved. |
| 113 | + handler.new VoteStreakConfigLoader().load(root); |
| 114 | + } |
| 115 | + |
| 116 | + @Test |
| 117 | + void configLoader_loadsDefinition_andGetDefinitionWorks() { |
| 118 | + MemoryConfiguration root = rootWithOneStreak("DailyStreak", "DAILY", true, 5, 2, 0, 0); |
| 119 | + loadFromRoot(root); |
| 120 | + |
| 121 | + // getDefinition() is broken due to key casing mismatch in handler: byId.put(id) |
| 122 | + // vs lookup lowercased |
| 123 | + VoteStreakDefinition def = handler.getById().get("DailyStreak"); |
| 124 | + assertNotNull(def); |
| 125 | + |
| 126 | + assertEquals("DailyStreak", def.getId()); |
| 127 | + assertEquals(VoteStreakType.DAILY, def.getType()); |
| 128 | + assertTrue(def.isEnabled()); |
| 129 | + assertEquals(5, def.getRequiredAmount()); |
| 130 | + assertEquals(2, def.getVotesRequired()); |
| 131 | + } |
| 132 | + |
| 133 | + @Test |
| 134 | + void processVote_whenAlreadyCountedThisPeriod_doesNotIncrementStreakAgain() { |
| 135 | + MemoryConfiguration root = rootWithOneStreak("test", "DAILY", true, 9999, 2, 0, 0); |
| 136 | + loadFromRoot(root); |
| 137 | + |
| 138 | + VoteStreakDefinition def = handler.getById().get("test"); |
| 139 | + assertNotNull(def); |
| 140 | + |
| 141 | + String col = handler.getColumnName(def); |
| 142 | + |
| 143 | + Map<String, String> backing = new HashMap<>(); |
| 144 | + VotingPluginUser user = mapBackedUser(UUID.randomUUID(), "Ben", backing); |
| 145 | + |
| 146 | + backing.put(col, "2026-01-10|5|1|true||0"); |
| 147 | + |
| 148 | + handler.processVote(user, System.currentTimeMillis(), UUID.randomUUID()); |
| 149 | + |
| 150 | + String[] p = parseState(handler.readStateString(user, col)); |
| 151 | + assertEquals("5", p[1], "streakCount must not increment again when already counted"); |
| 152 | + assertEquals("1", p[2], "votesThisPeriod should NOT increment once already countedThisPeriod=true"); |
| 153 | + assertEquals("true", p[3]); |
| 154 | + } |
| 155 | + |
| 156 | + @Test |
| 157 | + void processVote_acceptsOldFiveFieldFormat_andUpgradesState() { |
| 158 | + MemoryConfiguration root = rootWithOneStreak("test", "DAILY", true, 9999, 2, 0, 0); |
| 159 | + loadFromRoot(root); |
| 160 | + |
| 161 | + VoteStreakDefinition def = handler.getById().get("test"); |
| 162 | + assertNotNull(def); |
| 163 | + |
| 164 | + String col = handler.getColumnName(def); |
| 165 | + |
| 166 | + Map<String, String> backing = new HashMap<>(); |
| 167 | + VotingPluginUser user = mapBackedUser(UUID.randomUUID(), "Ben", backing); |
| 168 | + |
| 169 | + backing.put(col, "2026-01-10|5|true||2"); |
| 170 | + |
| 171 | + handler.processVote(user, System.currentTimeMillis(), UUID.randomUUID()); |
| 172 | + |
| 173 | + String[] p = parseState(handler.readStateString(user, col)); |
| 174 | + assertEquals("2026-01-10", p[0]); |
| 175 | + assertEquals("5", p[1], "streakCount should not increment (already counted)"); |
| 176 | + assertEquals("1", p[2], "votesThisPeriod upgraded-from-old (counted=true => 1) and ignored on vote"); |
| 177 | + assertEquals("true", p[3]); |
| 178 | + assertEquals("2", p[5], "missesUsed preserved"); |
| 179 | + } |
| 180 | + |
| 181 | +} |
0 commit comments