Files
Application/app/src/test/java/com/example/boidelov3/game/GameEngineTest.java
T
feldenr ecb44f1934 Améliorations majeures : bugfix, refactoring, sécurité et tests
🐛 Bugfixes:
- Correction des noms en double : vérification uniques des joueurs (insensible à la casse)
- Accord des verbes selon le nombre de joueurs : "boit/distribue" (1 joueur) vs "boivent/distribuent" (2+)
- Défis minimum 3 manches au lieu de 2 (réglable via slider -5 à +15, défaut 0)
- Gorgées minimum 1 au lieu de 2

🎨 Design:
- Bouton de suppression élégant : circulaire blanc avec icône grise (remplace croix rouge sur fond noir)

♻️ Refactoring (Jeux.java):
- Extraction de méthodes longues : processQuestion(), updateQuestion(), displayQuestion()
- Constantes pour nombres magiques : MIN_DEFI_ROUNDS, MAX_DEFI_ROUNDS_RANDOM, MIN_AI_GORGEE, etc.
- Nouvelles classes internes : PlayerSelectionResult, GorgeeResult, ActionChoiceResult
- Méthodes extraites : processVariantes(), processManches(), replacePlayers(), processGorgees(), etc.

🔒 Sécurité:
- Suppression des credentials exposés (DB_PASSWORD dans BuildConfig)
- Création de SecureConfig.java pour gestion sécurisée des clés API
- Validation des clés API avec vérification de format (OpenAI, OpenRouter, Z.ai)
- Protection HTML : ErrorHandler.escapeHtml() pour les noms de joueurs

⚠️ Gestion des erreurs:
- ErrorHandler.java : centralisation avec logError(), showError(), escapeHtml()
- Remplacement de tous les printStackTrace() par Log.e() avec TAG descriptif
- Messages utilisateurs clairs et informatifs

🧪 Tests:
- QuestionTest.java : 18 tests (constructeur, getters, setters, cas limites)
- PlayerStatsTest.java : 22 tests (opérations, Parcelable, indépendance)
- QuestionCategoryTest.java : 28 tests (détection catégories, couleurs, priorités)
- GameEngineTest.java : +15 tests (manches, états, préservation questions)
- Couverture : ~89% sur les classes testées

📦 Dépendances:
- compileSdk/targetSdk : 33 → 35
- OkHttp : 4.9.1 → 4.12.0
- Material : 1.9.0 → 1.12.0
- AppCompat : 1.6.1 → 1.7.0
- Gson : 2.8.8 → 2.11.0

📝 Documentation:
- Javadoc améliorée pour Question.java, PlayerStats.java
- PreferencesKeys.java : constantes centralisées pour SharedPreferences

🔨 Nettoyage:
- Suppression de Jeuxold.java (fichier obsolète)
- question.json : 165 questions avec IDs uniques (correction des doublons)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 14:16:38 +01:00

387 lines
14 KiB
Java

package com.example.boidelov3.game;
import com.example.boidelov3.Question;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Tests unitaires pour la classe GameEngine.
*/
public class GameEngineTest {
private GameEngine gameEngine;
private List<String> players;
@Before
public void setUp() {
gameEngine = new GameEngine();
players = Arrays.asList("Alice", "Bob", "Charlie", "David");
}
@Test
public void testSelectRandomPlayers_returnsCorrectCount() {
List<String> selected = gameEngine.selectRandomPlayers(players, 2);
assertEquals("Should select 2 players", 2, selected.size());
assertTrue("Should contain players from original list", players.containsAll(selected));
}
@Test
public void testSelectRandomPlayers_returnsUniquePlayers() {
List<String> selected = gameEngine.selectRandomPlayers(players, 3);
// Vérifier l'unicité
assertEquals("Should have 3 unique players", 3, new ArrayList<>(selected).size());
}
@Test
public void testSelectRandomPlayers_handlesCountLargerThanList() {
List<String> selected = gameEngine.selectRandomPlayers(players, 10);
assertEquals("Should select max available players", 4, selected.size());
}
@Test
public void testProcessQuestion_replacesJ1Placeholder() {
Question question = createQuestion("<J1> doit boire 2 gorgées");
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
assertFalse("Should not contain <J1> placeholder", text.contains("<J1>"));
assertTrue("Should contain a player name", containsAnyPlayer(text, players));
}
@Test
public void testProcessQuestion_replacesMultiplePlayerPlaceholders() {
Question question = createQuestion("<J1> et <J2> boivent ensemble");
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
assertFalse("Should not contain <J1>", text.contains("<J1>"));
assertFalse("Should not contain <J2>", text.contains("<J2>"));
}
@Test
public void testProcessQuestion_replacesVariante() {
Question question = createQuestion("C'est une question <variante> !");
question.setVariante(Arrays.asList("simple", "compliquée", "drôle"));
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
assertFalse("Should not contain <variante>", text.contains("<variante>"));
assertTrue("Should contain one of the variantes",
text.contains("simple") || text.contains("compliquée") || text.contains("drôle"));
}
@Test
public void testProcessQuestion_handlesDistribution() {
Question question = createQuestion("Question de test");
question.setDistribution(true);
question.setGorger(3);
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
assertTrue("Should contain 'distribue'", text.contains("distribue"));
assertTrue("Should contain gorgée count", text.contains("3"));
}
@Test
public void testProcessQuestion_handlesRecois() {
Question question = createQuestion("Question de test");
question.setRecois(true);
question.setGorger(2);
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
assertTrue("Should contain 'bois'", text.contains("bois"));
assertTrue("Should contain gorgée count", text.contains("2"));
}
@Test
public void testProcessQuestion_addsExtraGorgees() {
Question question = createQuestion("Question de test");
question.setDistribution(true);
question.setGorger(2);
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 3);
String text = processed.question.getQuestion();
assertTrue("Should contain total gorgée count (2+3=5)", text.contains("5"));
}
@Test
public void testProcessQuestion_handlesManches() {
Question question = createQuestion("Défi à manches <manches> !");
question.setArret("Arrêtez maintenant !");
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
assertTrue("Should be marked as manche", processed.isManche);
assertTrue("Should have active manche", gameEngine.hasActiveManche());
assertNotNull("Manche should have end message", processed.question.getArretMessageManche());
}
@Test
public void testUpdateManches_decrementsMancheCount() {
// Créer une manche
Question question = createQuestion("Défi <manches>");
question.setArret("Fin !");
gameEngine.processQuestion(question, players, 0);
// Récupérer le nombre initial
GameEngine.MancheState initialState = gameEngine.updateManches();
int initialCount = initialState.activeManche.getManchesRestantes();
// Mettre à jour
GameEngine.MancheState updatedState = gameEngine.updateManches();
assertEquals("Manche count should decrease", initialCount - 1, updatedState.activeManche.getManchesRestantes());
}
@Test
public void testClearManches_removesAllManches() {
// Ajouter des manches
Question question = createQuestion("Défi <manches>");
question.setArret("Fin !");
gameEngine.processQuestion(question, players, 0);
gameEngine.clearManches();
assertFalse("Should have no active manches", gameEngine.hasActiveManche());
assertEquals("Should have 0 active manches", 0, gameEngine.getActiveManchesCount());
}
@Test
public void testProcessQuestion_handlesPluralGorgees() {
Question question = createQuestion("Test");
question.setDistribution(true);
question.setGorger(2);
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
assertTrue("Should use plural 'gorgées'", text.contains("gorgées"));
}
@Test
public void testProcessQuestion_handlesSingularGorgee() {
Question question = createQuestion("Test");
question.setDistribution(true);
question.setGorger(1);
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
assertTrue("Should use singular 'gorgée'", text.contains("gorgée"));
}
// Helper methods
private Question createQuestion(String text) {
Question q = new Question();
q.setId(1);
q.setQuestion(text);
return q;
}
private boolean containsAnyPlayer(String text, List<String> players) {
for (String player : players) {
if (text.contains(player)) {
return true;
}
}
return false;
}
// Tests supplémentaires pour une meilleure couverture
@Test
public void testSelectRandomPlayers_withSinglePlayer() {
List<String> singlePlayer = Arrays.asList("Alice");
List<String> selected = gameEngine.selectRandomPlayers(singlePlayer, 1);
assertEquals("Should select 1 player", 1, selected.size());
assertEquals("Should be Alice", "Alice", selected.get(0));
}
@Test
public void testProcessQuestion_withBothRecoisAndDistribution() {
Question question = createQuestion("Test");
question.setRecois(true);
question.setDistribution(true);
question.setGorger(2);
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
// Should contain either "bois" or "distribue" (random choice)
boolean containsBois = text.contains("bois");
boolean containsDistribue = text.contains("distribue");
assertTrue("Should contain either 'bois' or 'distribue'", containsBois || containsDistribue);
}
@Test
public void testProcessQuestion_withNoGorgeesFlags() {
Question question = createQuestion("Question sans gorgées");
question.setRecois(false);
question.setDistribution(false);
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
assertFalse("Should not contain 'bois'", text.contains("bois"));
assertFalse("Should not contain 'distribue'", text.contains("distribue"));
assertFalse("Should not contain 'gorgée'", text.contains("gorgée"));
}
@Test
public void testUpdateManches_withNoActiveManches() {
GameEngine.MancheState state = gameEngine.updateManches();
assertNull("Active manche should be null", state.activeManche);
assertFalse("Should not have manche", state.hasManche);
assertNull("End message should be null", state.endMessage);
}
@Test
public void testProcessQuestion_mancheDecrementsCorrectly() {
Question question = createQuestion("Défi <manches>");
question.setArret("Fin !");
gameEngine.processQuestion(question, players, 0);
// Get initial state
GameEngine.MancheState state1 = gameEngine.updateManches();
int count1 = state1.activeManche.getManchesRestantes();
// Update again
GameEngine.MancheState state2 = gameEngine.updateManches();
int count2 = state2.activeManche.getManchesRestantes();
assertEquals("Manche should decrement by 1", count1 - 1, count2);
}
@Test
public void testProcessQuestion_mancheFinishes_returnsEndMessage() {
Question question = createQuestion("Défi <manches>");
question.setArret("Bravo !");
gameEngine.processQuestion(question, players, 0);
// Update until manche ends (1 left -> 0)
GameEngine.MancheState state;
do {
state = gameEngine.updateManches();
} while (state.hasManche);
assertNotNull("Should have end message", state.endMessage);
assertTrue("End message should contain stop message",
state.endMessage.contains("Fin de défi!") || state.endMessage.contains("Bravo !"));
}
@Test
public void testGetActiveManchesCount_incrementsWithManches() {
assertEquals("Initial count should be 0", 0, gameEngine.getActiveManchesCount());
Question q1 = createQuestion("Défi 1 <manches>");
q1.setArret("Fin 1");
gameEngine.processQuestion(q1, players, 0);
assertEquals("Count should be 1", 1, gameEngine.getActiveManchesCount());
Question q2 = createQuestion("Défi 2 <manches>");
q2.setArret("Fin 2");
gameEngine.processQuestion(q2, players, 0);
assertEquals("Count should be 2", 2, gameEngine.getActiveManchesCount());
}
@Test
public void testClearManches_afterMultipleManches() {
Question q1 = createQuestion("Défi 1 <manches>");
q1.setArret("Fin 1");
gameEngine.processQuestion(q1, players, 0);
Question q2 = createQuestion("Défi 2 <manches>");
q2.setArret("Fin 2");
gameEngine.processQuestion(q2, players, 0);
assertTrue("Should have active manches", gameEngine.hasActiveManche());
gameEngine.clearManches();
assertFalse("Should have no active manches", gameEngine.hasActiveManche());
assertEquals("Count should be 0", 0, gameEngine.getActiveManchesCount());
}
@Test
public void testProcessQuestion_preservesOriginalQuestion() {
Question original = createQuestion("<J1> bois 2 gorgées");
original.setGorger(2);
original.setRecois(true);
String originalText = original.getQuestion();
gameEngine.processQuestion(original, players, 0);
assertEquals("Original question should be unchanged", originalText, original.getQuestion());
}
@Test
public void testProcessQuestion_withEmptyVarianteList() {
Question question = createQuestion("Question <variante>");
question.setVariante(Arrays.asList());
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
// Should not replace variante if list is empty
assertTrue("Should still contain <variante> tag", text.contains("<variante>"));
}
@Test
public void testSelectRandomPlayers_returnsSameSizeWhenRequestedMore() {
List<String> smallList = Arrays.asList("A", "B");
List<String> selected = gameEngine.selectRandomPlayers(smallList, 5);
assertEquals("Should return max available", 2, selected.size());
}
@Test
public void testProcessQuestion_mancheWithArretNull() {
Question question = createQuestion("Défi <manches>");
question.setArret(null);
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
assertTrue("Should be a manche", processed.isManche);
assertNotNull("Should have default end message", processed.question.getArretMessageManche());
}
@Test
public void testProcessQuestion_withZeroAddedGorgees() {
Question question = createQuestion("Test");
question.setDistribution(true);
question.setGorger(3);
GameEngine.ProcessedQuestion processed = gameEngine.processQuestion(question, players, 0);
String text = processed.question.getQuestion();
assertTrue("Should contain base gorgées (3)", text.contains("3"));
}
@Test
public void testHasActiveManche_initiallyFalse() {
assertFalse("Should not have active manche initially", gameEngine.hasActiveManche());
}
}