diff --git a/.gitignore b/.gitignore index bf178ec..1fe2b8c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ *.iml .vscode/ .DS_Store +MSP3/ diff --git a/pom.xml b/pom.xml index 68b6a65..839051c 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,8 @@ UTF-8 5.1.0 8.0.33 + 5.10.2 + 5.11.0 @@ -63,6 +65,14 @@ maven-shade-plugin 3.5.1 + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + -Dnet.bytebuddy.experimental=true + + diff --git a/spigot/pom.xml b/spigot/pom.xml index 062042d..0a84672 100644 --- a/spigot/pom.xml +++ b/spigot/pom.xml @@ -42,6 +42,18 @@ 2.11.6 provided + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + diff --git a/spigot/src/test/java/fr/votenetwork/spigot/VoteConfigTest.java b/spigot/src/test/java/fr/votenetwork/spigot/VoteConfigTest.java new file mode 100644 index 0000000..c5c7939 --- /dev/null +++ b/spigot/src/test/java/fr/votenetwork/spigot/VoteConfigTest.java @@ -0,0 +1,70 @@ +package fr.votenetwork.spigot; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class VoteConfigTest { + + @Test + void defaultsAreUsedWhenKeysAbsent() { + VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class); + when(plugin.getConfig()).thenReturn(new YamlConfiguration()); + + VoteConfig config = new VoteConfig(plugin); + + assertTrue(config.isMysqlStorage()); + assertEquals("127.0.0.1", config.getMysqlHost()); + assertEquals(3306, config.getMysqlPort()); + assertEquals("votenetwork", config.getMysqlDatabase()); + assertEquals(100, config.getVotePartyVotesRequired()); + assertFalse(config.isPerServerScope()); + assertFalse(config.isVoteBroadcastEnabled()); + } + + @Test + void mysqlStorageIsFalseOnlyForYaml() { + YamlConfiguration yaml = new YamlConfiguration(); + yaml.set("storage.type", "yaml"); + VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class); + when(plugin.getConfig()).thenReturn(yaml); + + VoteConfig config = new VoteConfig(plugin); + + assertFalse(config.isMysqlStorage()); + assertEquals("yaml", config.getStorageType()); + } + + @Test + void perServerScopeReadFromConfig() { + YamlConfiguration yaml = new YamlConfiguration(); + yaml.set("storage.pending-votes-scope", "per-server"); + yaml.set("server-name", "gen1"); + VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class); + when(plugin.getConfig()).thenReturn(yaml); + + VoteConfig config = new VoteConfig(plugin); + + assertTrue(config.isPerServerScope()); + assertEquals("gen1", config.getServerName()); + } + + @Test + void messagesAreColorizedAndPlaceholdersPreserved() { + YamlConfiguration yaml = new YamlConfiguration(); + yaml.set("messages.claim-success", "&aTu as recupere &e%amount% &avotes !"); + VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class); + when(plugin.getConfig()).thenReturn(yaml); + + VoteConfig config = new VoteConfig(plugin); + String message = config.getMessageClaimSuccess(); + + assertFalse(message.contains("&a"), "les codes couleur '&' doivent etre traduits"); + assertTrue(message.contains("%amount%"), "le placeholder %amount% doit rester intact pour etre remplace plus tard"); + } +} diff --git a/spigot/src/test/java/fr/votenetwork/spigot/storage/YamlPendingVoteStoreTest.java b/spigot/src/test/java/fr/votenetwork/spigot/storage/YamlPendingVoteStoreTest.java new file mode 100644 index 0000000..d8f0b4c --- /dev/null +++ b/spigot/src/test/java/fr/votenetwork/spigot/storage/YamlPendingVoteStoreTest.java @@ -0,0 +1,67 @@ +package fr.votenetwork.spigot.storage; + +import fr.votenetwork.spigot.VoteNetworkSpigot; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class YamlPendingVoteStoreTest { + + @Test + void peekReturnsZeroWhenNothingStored(@TempDir File dataFolder) { + YamlPendingVoteStore store = newStore(dataFolder); + UUID uuid = UUID.randomUUID(); + + AtomicInteger result = new AtomicInteger(-1); + store.peek(uuid, result::set, e -> { throw new AssertionError(e); }); + + assertEquals(0, result.get()); + } + + @Test + void fetchAndClearResetsCounterToZeroAfterReading(@TempDir File dataFolder) throws Exception { + YamlPendingVoteStore store = newStore(dataFolder); + UUID uuid = UUID.randomUUID(); + + writeInitialVotes(dataFolder, uuid, 3); + + AtomicInteger firstRead = new AtomicInteger(-1); + store.fetchAndClear(uuid, firstRead::set, e -> { throw new AssertionError(e); }); + assertEquals(3, firstRead.get()); + + AtomicInteger secondRead = new AtomicInteger(-1); + store.peek(uuid, secondRead::set, e -> { throw new AssertionError(e); }); + assertEquals(0, secondRead.get(), "fetchAndClear doit remettre le compteur a 0"); + } + + @Test + void testConnectionReportsWritableDirectory(@TempDir File dataFolder) { + YamlPendingVoteStore store = newStore(dataFolder); + + AtomicReference success = new AtomicReference<>(); + store.testConnection((ok, message) -> success.set(ok)); + + assertEquals(Boolean.TRUE, success.get(), "un dossier temporaire accessible en ecriture doit etre rapporte comme fonctionnel"); + } + + private YamlPendingVoteStore newStore(File dataFolder) { + VoteNetworkSpigot plugin = mock(VoteNetworkSpigot.class); + when(plugin.getDataFolder()).thenReturn(dataFolder); + return new YamlPendingVoteStore(plugin); + } + + private void writeInitialVotes(File dataFolder, UUID uuid, int votes) throws Exception { + org.bukkit.configuration.file.YamlConfiguration yaml = new org.bukkit.configuration.file.YamlConfiguration(); + yaml.set("players." + uuid + ".votes", votes); + File file = new File(dataFolder, "pending-votes.yml"); + yaml.save(file); + } +} diff --git a/velocity/pom.xml b/velocity/pom.xml index 00d22f3..0c50857 100644 --- a/velocity/pom.xml +++ b/velocity/pom.xml @@ -36,6 +36,12 @@ 2.10.1 provided + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + diff --git a/velocity/src/test/java/fr/votenetwork/velocity/VoteConfigTest.java b/velocity/src/test/java/fr/votenetwork/velocity/VoteConfigTest.java new file mode 100644 index 0000000..1b62af7 --- /dev/null +++ b/velocity/src/test/java/fr/votenetwork/velocity/VoteConfigTest.java @@ -0,0 +1,104 @@ +package fr.votenetwork.velocity; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.slf4j.helpers.NOPLogger; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class VoteConfigTest { + + @Test + void loadCopiesDefaultsWhenFileMissing(@TempDir Path dataDir) { + VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER); + config.load(); + + assertTrue(config.isMysqlStorage()); + assertEquals("jdbc:mysql://127.0.0.1:3306/votenetwork?useSSL=false&autoReconnect=true&characterEncoding=utf8", + config.getJdbcUrl()); + assertTrue(Files.exists(dataDir.resolve("config.properties"))); + } + + @Test + void perServerScopeFallsBackToGlobalWhenServerListEmpty(@TempDir Path dataDir) throws IOException { + writeConfig(dataDir, """ + storage.type=mysql + mysql.host=127.0.0.1 + mysql.port=3306 + mysql.database=votenetwork + mysql.user=root + mysql.password=changeme + mysql.pool-size=5 + direct-vote.servers= + pending-votes.scope=per-server + """); + + VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER); + config.load(); + + assertFalse(config.isPerServerScope(), "sans direct-vote.servers, per-server doit retomber sur global"); + } + + @Test + void perServerScopeAppliesWhenServersListed(@TempDir Path dataDir) throws IOException { + writeConfig(dataDir, """ + storage.type=mysql + mysql.host=127.0.0.1 + mysql.port=3306 + mysql.database=votenetwork + mysql.user=root + mysql.password=changeme + mysql.pool-size=5 + direct-vote.servers=gen1, GEN2 + pending-votes.scope=per-server + """); + + VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER); + config.load(); + + assertTrue(config.isPerServerScope()); + assertTrue(config.isDirectVoteServer("gen1")); + assertTrue(config.isDirectVoteServer("Gen2"), "la comparaison doit ignorer la casse"); + assertFalse(config.isDirectVoteServer("lobby")); + } + + @Test + void isDirectVoteServerAllowsAllWhenListEmpty(@TempDir Path dataDir) throws IOException { + writeConfig(dataDir, """ + storage.type=mysql + direct-vote.servers= + """); + + VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER); + config.load(); + + assertTrue(config.isDirectVoteServer("n_importe_quoi")); + } + + @Test + void mergeAddsMissingKeysWithoutTouchingExistingOnes(@TempDir Path dataDir) throws IOException { + writeConfig(dataDir, """ + mysql.password=ne-pas-toucher + """); + + VoteConfig config = new VoteConfig(dataDir, NOPLogger.NOP_LOGGER); + config.load(); + + String fileContent = Files.readString(dataDir.resolve("config.properties"), StandardCharsets.UTF_8); + assertTrue(fileContent.contains("mysql.password=ne-pas-toucher"), "la valeur existante ne doit jamais etre ecrasee"); + assertTrue(fileContent.contains("pending-votes.scope="), "les cles manquantes doivent etre ajoutees"); + assertEquals("ne-pas-toucher", config.getPassword()); + } + + private void writeConfig(Path dataDir, String content) throws IOException { + Files.createDirectories(dataDir); + Files.writeString(dataDir.resolve("config.properties"), content, StandardCharsets.UTF_8); + } +} diff --git a/velocity/src/test/java/fr/votenetwork/velocity/YamlPendingVoteStoreTest.java b/velocity/src/test/java/fr/votenetwork/velocity/YamlPendingVoteStoreTest.java new file mode 100644 index 0000000..6fa86a2 --- /dev/null +++ b/velocity/src/test/java/fr/votenetwork/velocity/YamlPendingVoteStoreTest.java @@ -0,0 +1,55 @@ +package fr.votenetwork.velocity; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.slf4j.helpers.NOPLogger; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class YamlPendingVoteStoreTest { + + @Test + void addPendingVoteCreatesEntryAndIncrements(@TempDir Path dataDir) + throws ExecutionException, InterruptedException, TimeoutException, IOException { + YamlPendingVoteStore store = new YamlPendingVoteStore(dataDir, NOPLogger.NOP_LOGGER); + UUID uuid = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"); + + store.addPendingVote(uuid, "Notch").get(5, TimeUnit.SECONDS); + + String content = Files.readString(dataDir.resolve("pending-votes.yml"), StandardCharsets.UTF_8); + assertTrue(content.contains(uuid.toString())); + assertTrue(content.contains("name: Notch")); + assertTrue(content.contains("votes: 1")); + + store.addPendingVote(uuid, "Notch").get(5, TimeUnit.SECONDS); + + content = Files.readString(dataDir.resolve("pending-votes.yml"), StandardCharsets.UTF_8); + assertTrue(content.contains("votes: 2"), "un deuxieme vote hors ligne doit incrementer le compteur existant"); + } + + @Test + void addPendingVoteHandlesTwoDifferentPlayersIndependently(@TempDir Path dataDir) + throws ExecutionException, InterruptedException, TimeoutException, IOException { + YamlPendingVoteStore store = new YamlPendingVoteStore(dataDir, NOPLogger.NOP_LOGGER); + UUID uuidA = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"); + UUID uuidB = UUID.fromString("853c80ef-3c37-49fd-aa49-938b674adae6"); + + store.addPendingVote(uuidA, "Sar_Tron").get(5, TimeUnit.SECONDS); + store.addPendingVote(uuidB, "Notch").get(5, TimeUnit.SECONDS); + + String content = Files.readString(dataDir.resolve("pending-votes.yml"), StandardCharsets.UTF_8); + assertTrue(content.contains(uuidA.toString())); + assertTrue(content.contains(uuidB.toString())); + assertTrue(content.contains("name: Sar_Tron")); + assertTrue(content.contains("name: Notch")); + } +}