Ajoute des tests unitaires JUnit 5 (CP9 - plans de tests)

14 tests couvrant VoteConfig (Spigot + Velocity) et YamlPendingVoteStore
(Spigot + Velocity) : valeurs par defaut, portee per-server/global,
merge de config sans ecraser l'existant, incrementation/reset des
votes en attente, colorisation des messages.

Mockito pour VoteConfig/YamlPendingVoteStore cote Spigot (mock de
VoteNetworkSpigot, pas besoin de serveur Bukkit reel). Cote Velocity,
tests directs sans framework (classes deja decouplees de Velocity-API).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
SarTron-NorthBlue
2026-07-13 09:14:12 +04:00
co-authored by Claude Sonnet 5
parent d5f6e0cfb3
commit 5abe49fdd0
8 changed files with 325 additions and 0 deletions
+1
View File
@@ -5,3 +5,4 @@ dist/
*.iml
.vscode/
.DS_Store
MSP3/
+10
View File
@@ -37,6 +37,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hikaricp.version>5.1.0</hikaricp.version>
<mysql.version>8.0.33</mysql.version>
<junit.version>5.10.2</junit.version>
<mockito.version>5.11.0</mockito.version>
</properties>
<repositories>
@@ -63,6 +65,14 @@
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>-Dnet.bytebuddy.experimental=true</argLine>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
+12
View File
@@ -42,6 +42,18 @@
<version>2.11.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
@@ -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");
}
}
@@ -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<Boolean> 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);
}
}
+6
View File
@@ -36,6 +36,12 @@
<version>2.10.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
@@ -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);
}
}
@@ -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"));
}
}