Configure Maven repository for unionflow-server-api dependency

This commit is contained in:
dahoud
2025-12-10 01:08:17 +00:00
commit 4a0c5f9d33
320 changed files with 33373 additions and 0 deletions

View File

@@ -0,0 +1,400 @@
package dev.lions.unionflow.server.resource;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.Evenement.StatutEvenement;
import dev.lions.unionflow.server.entity.Evenement.TypeEvenement;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.EvenementRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.*;
/**
* Tests d'intégration pour EvenementResource
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class EvenementResourceTest {
private static final String BASE_PATH = "/api/evenements";
@Inject EvenementRepository evenementRepository;
@Inject OrganisationRepository organisationRepository;
private Evenement testEvenement;
private Organisation testOrganisation;
@BeforeEach
@Transactional
void setupTestData() {
// Créer une organisation de test
testOrganisation =
Organisation.builder()
.nom("Organisation Test Événements")
.typeOrganisation("ASSOCIATION")
.statut("ACTIF")
.email("org-events-" + System.currentTimeMillis() + "@test.com")
.build();
testOrganisation.setDateCreation(LocalDateTime.now());
testOrganisation.setActif(true);
organisationRepository.persist(testOrganisation);
// Créer un événement de test
testEvenement =
Evenement.builder()
.titre("Événement Test")
.description("Description de l'événement de test")
.dateDebut(LocalDateTime.now().plusDays(7))
.dateFin(LocalDateTime.now().plusDays(7).plusHours(3))
.lieu("Lieu Test")
.typeEvenement(TypeEvenement.REUNION)
.statut(StatutEvenement.PLANIFIE)
.capaciteMax(50)
.prix(BigDecimal.valueOf(5000))
.organisation(testOrganisation)
.build();
testEvenement.setDateCreation(LocalDateTime.now());
testEvenement.setActif(true);
evenementRepository.persist(testEvenement);
}
@AfterEach
@Transactional
void cleanupTestData() {
// Supprimer tous les événements liés à l'organisation avant de supprimer l'organisation
if (testOrganisation != null && testOrganisation.getId() != null) {
// Supprimer tous les événements de cette organisation
java.util.List<Evenement> evenements =
evenementRepository.findByOrganisation(testOrganisation.getId());
for (Evenement evt : evenements) {
evenementRepository.delete(evt);
}
}
// Supprimer l'événement de test s'il existe encore
if (testEvenement != null && testEvenement.getId() != null) {
Evenement evtToDelete = evenementRepository.findById(testEvenement.getId());
if (evtToDelete != null) {
evenementRepository.delete(evtToDelete);
}
}
// Supprimer l'organisation après avoir supprimé tous ses événements
if (testOrganisation != null && testOrganisation.getId() != null) {
Organisation orgToDelete = organisationRepository.findById(testOrganisation.getId());
if (orgToDelete != null) {
organisationRepository.delete(orgToDelete);
}
}
}
@Test
@Order(1)
@DisplayName("GET /api/evenements/test doit retourner un statut de connectivité")
void testConnectivity() {
given()
.when()
.get(BASE_PATH + "/test")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("status", equalTo("success"))
.body("message", notNullValue());
}
@Test
@Order(2)
@DisplayName("GET /api/evenements/count doit retourner le nombre d'événements")
void testCountEvenements() {
given()
.when()
.get(BASE_PATH + "/count")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("count", greaterThanOrEqualTo(0))
.body("status", equalTo("success"));
}
@Test
@Order(3)
@DisplayName("GET /api/evenements doit retourner la liste des événements")
void testListerEvenements() {
given()
.queryParam("page", 0)
.queryParam("size", 20)
.when()
.get(BASE_PATH)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("$", notNullValue());
}
@Test
@Order(4)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/evenements/{id} doit retourner un événement existant")
void testObtenirEvenement() {
UUID eventId = testEvenement.getId();
given()
.pathParam("id", eventId)
.when()
.get(BASE_PATH + "/{id}")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("id", equalTo(eventId.toString()))
.body("titre", equalTo("Événement Test"));
}
@Test
@Order(5)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/evenements/{id} doit retourner 404 pour un ID inexistant")
void testObtenirEvenementInexistant() {
UUID fakeId = UUID.randomUUID();
given()
.pathParam("id", fakeId)
.when()
.get(BASE_PATH + "/{id}")
.then()
.statusCode(404);
}
@Test
@Order(6)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("POST /api/evenements doit créer un nouvel événement")
void testCreerEvenement() {
String eventJson =
"""
{
"titre": "Nouvel Événement Test",
"description": "Description du nouvel événement",
"dateDebut": "%s",
"dateFin": "%s",
"lieu": "Nouveau Lieu",
"typeEvenement": "REUNION",
"statut": "PLANIFIE",
"capaciteMax": 100,
"prix": 10000,
"organisationId": "%s"
}
"""
.formatted(
LocalDateTime.now().plusDays(14).toString(),
LocalDateTime.now().plusDays(14).plusHours(4).toString(),
testOrganisation.getId().toString());
UUID createdId =
UUID.fromString(
given()
.contentType(ContentType.JSON)
.body(eventJson)
.when()
.post(BASE_PATH)
.then()
.statusCode(201)
.contentType(ContentType.JSON)
.body("titre", equalTo("Nouvel Événement Test"))
.body("id", notNullValue())
.extract()
.path("id"));
// Nettoyer l'événement créé
Evenement created = evenementRepository.findById(createdId);
if (created != null) {
evenementRepository.delete(created);
}
}
@Test
@Order(7)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("POST /api/evenements doit retourner 400 pour données invalides")
void testCreerEvenementInvalide() {
String invalidEventJson =
"""
{
"titre": "",
"description": "Description",
"dateDebut": "%s"
}
"""
.formatted(LocalDateTime.now().plusDays(1).toString());
given()
.contentType(ContentType.JSON)
.body(invalidEventJson)
.when()
.post(BASE_PATH)
.then()
.statusCode(400);
}
@Test
@Order(8)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("PUT /api/evenements/{id} doit mettre à jour un événement")
void testModifierEvenement() {
UUID eventId = testEvenement.getId();
// Récupérer l'événement existant pour préserver l'organisation
Evenement existing = evenementRepository.findById(eventId);
String updatedEventJson =
"""
{
"titre": "Événement Modifié",
"description": "Description modifiée",
"dateDebut": "%s",
"dateFin": "%s",
"lieu": "Lieu Modifié",
"typeEvenement": "REUNION",
"statut": "PLANIFIE",
"capaciteMax": 75,
"prix": 7500,
"actif": true,
"visiblePublic": true,
"inscriptionRequise": false
}
"""
.formatted(
LocalDateTime.now().plusDays(10).toString(),
LocalDateTime.now().plusDays(10).plusHours(5).toString());
given()
.contentType(ContentType.JSON)
.pathParam("id", eventId)
.body(updatedEventJson)
.when()
.put(BASE_PATH + "/{id}")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("titre", equalTo("Événement Modifié"))
.body("lieu", equalTo("Lieu Modifié"));
}
@Test
@Order(9)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("PUT /api/evenements/{id} doit retourner 404 pour ID inexistant")
void testModifierEvenementInexistant() {
UUID fakeId = UUID.randomUUID();
String updatedEventJson =
"""
{
"titre": "Événement Test",
"dateDebut": "%s",
"actif": true,
"visiblePublic": true,
"inscriptionRequise": false
}
"""
.formatted(LocalDateTime.now().plusDays(1).toString());
given()
.contentType(ContentType.JSON)
.pathParam("id", fakeId)
.body(updatedEventJson)
.when()
.put(BASE_PATH + "/{id}")
.then()
.statusCode(404);
}
@Test
@Order(10)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("DELETE /api/evenements/{id} doit supprimer un événement")
void testSupprimerEvenement() {
// Créer un événement temporaire pour la suppression
Evenement tempEvent =
Evenement.builder()
.titre("Événement à Supprimer")
.description("Description")
.dateDebut(LocalDateTime.now().plusDays(5))
.typeEvenement(TypeEvenement.REUNION)
.statut(StatutEvenement.PLANIFIE)
.organisation(testOrganisation)
.build();
tempEvent.setDateCreation(LocalDateTime.now());
tempEvent.setActif(true);
evenementRepository.persist(tempEvent);
UUID tempId = tempEvent.getId();
given()
.pathParam("id", tempId)
.when()
.delete(BASE_PATH + "/{id}")
.then()
.statusCode(204);
// Vérifier que l'événement a été supprimé
Evenement deleted = evenementRepository.findById(tempId);
assert deleted == null : "L'événement devrait être supprimé";
}
@Test
@Order(11)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("DELETE /api/evenements/{id} doit retourner 404 pour ID inexistant")
void testSupprimerEvenementInexistant() {
UUID fakeId = UUID.randomUUID();
given()
.pathParam("id", fakeId)
.when()
.delete(BASE_PATH + "/{id}")
.then()
.statusCode(404);
}
@Test
@Order(12)
@DisplayName("GET /api/evenements doit supporter la pagination")
void testPagination() {
given()
.queryParam("page", 0)
.queryParam("size", 5)
.when()
.get(BASE_PATH)
.then()
.statusCode(200)
.contentType(ContentType.JSON);
}
@Test
@Order(13)
@DisplayName("GET /api/evenements doit supporter le tri")
void testTri() {
given()
.queryParam("page", 0)
.queryParam("size", 10)
.queryParam("sort", "dateDebut")
.queryParam("direction", "desc")
.when()
.get(BASE_PATH)
.then()
.statusCode(200)
.contentType(ContentType.JSON);
}
}

View File

@@ -0,0 +1,334 @@
package dev.lions.unionflow.server.resource;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import java.time.LocalDate;
import java.util.List;
import org.junit.jupiter.api.*;
/**
* Tests d'intégration pour l'endpoint de recherche avancée des membres
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MembreResourceAdvancedSearchTest {
private static final String ADVANCED_SEARCH_ENDPOINT = "/api/membres/search/advanced";
@Test
@Order(1)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit fonctionner avec critères valides")
void testAdvancedSearchWithValidCriteria() {
MembreSearchCriteria criteria =
MembreSearchCriteria.builder().query("marie").statut("ACTIF").ageMin(20).ageMax(50).build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.queryParam("page", 0)
.queryParam("size", 20)
.queryParam("sort", "nom")
.queryParam("direction", "asc")
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("membres", notNullValue())
.body("totalElements", greaterThanOrEqualTo(0))
.body("totalPages", greaterThanOrEqualTo(0))
.body("currentPage", equalTo(0))
.body("pageSize", equalTo(20))
.body("hasNext", notNullValue())
.body("hasPrevious", equalTo(false))
.body("isFirst", equalTo(true))
.body("executionTimeMs", greaterThan(0))
.body("statistics", notNullValue())
.body("statistics.membresActifs", greaterThanOrEqualTo(0))
.body("statistics.membresInactifs", greaterThanOrEqualTo(0))
.body("criteria.query", equalTo("marie"))
.body("criteria.statut", equalTo("ACTIF"));
}
@Test
@Order(2)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit fonctionner avec critères multiples")
void testAdvancedSearchWithMultipleCriteria() {
MembreSearchCriteria criteria =
MembreSearchCriteria.builder()
.email("@unionflow.com")
.dateAdhesionMin(LocalDate.of(2020, 1, 1))
.dateAdhesionMax(LocalDate.of(2025, 12, 31))
.roles(List.of("ADMIN", "SUPER_ADMIN"))
.includeInactifs(false)
.build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.queryParam("page", 0)
.queryParam("size", 10)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("membres", notNullValue())
.body("totalElements", greaterThanOrEqualTo(0))
.body("criteria.email", equalTo("@unionflow.com"))
.body("criteria.roles", hasItems("ADMIN", "SUPER_ADMIN"))
.body("criteria.includeInactifs", equalTo(false));
}
@Test
@Order(3)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit gérer la pagination")
void testAdvancedSearchPagination() {
MembreSearchCriteria criteria =
MembreSearchCriteria.builder()
.statut("ACTIF") // Ajouter un critère valide
.includeInactifs(true) // Inclure tous les membres
.build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.queryParam("page", 0)
.queryParam("size", 2) // Petite taille pour tester la pagination
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("currentPage", equalTo(0))
.body("pageSize", equalTo(2))
.body("isFirst", equalTo(true))
.body("hasPrevious", equalTo(false));
}
@Test
@Order(4)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit gérer le tri")
void testAdvancedSearchSorting() {
MembreSearchCriteria criteria = MembreSearchCriteria.builder().statut("ACTIF").build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.queryParam("sort", "nom")
.queryParam("direction", "desc")
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("membres", notNullValue());
}
@Test
@Order(5)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit retourner 400 pour critères vides")
void testAdvancedSearchWithEmptyCriteria() {
MembreSearchCriteria emptyCriteria = MembreSearchCriteria.builder().build();
given()
.contentType(ContentType.JSON)
.body(emptyCriteria)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(400)
.contentType(ContentType.JSON)
.body("message", containsString("Au moins un critère de recherche doit être spécifié"));
}
@Test
@Order(6)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit retourner 400 pour critères invalides")
void testAdvancedSearchWithInvalidCriteria() {
MembreSearchCriteria invalidCriteria =
MembreSearchCriteria.builder()
.ageMin(50)
.ageMax(30) // Âge max < âge min
.build();
given()
.contentType(ContentType.JSON)
.body(invalidCriteria)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(400)
.contentType(ContentType.JSON)
.body("message", containsString("Critères de recherche invalides"));
}
@Test
@Order(7)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit retourner 400 pour body null")
void testAdvancedSearchWithNullBody() {
given()
.contentType(ContentType.JSON)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(400);
}
@Test
@Order(8)
@TestSecurity(
user = "marie.active@unionflow.com",
roles = {"MEMBRE_ACTIF"})
@DisplayName("POST /api/membres/search/advanced doit retourner 403 pour utilisateur non autorisé")
void testAdvancedSearchUnauthorized() {
MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("test").build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(403);
}
@Test
@Order(9)
@DisplayName(
"POST /api/membres/search/advanced doit retourner 401 pour utilisateur non authentifié")
void testAdvancedSearchUnauthenticated() {
MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("test").build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(401);
}
@Test
@Order(10)
@TestSecurity(
user = "admin@unionflow.com",
roles = {"ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit fonctionner pour ADMIN")
void testAdvancedSearchForAdmin() {
MembreSearchCriteria criteria = MembreSearchCriteria.builder().statut("ACTIF").build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("membres", notNullValue());
}
@Test
@Order(11)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit inclure le temps d'exécution")
void testAdvancedSearchExecutionTime() {
MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("test").build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("executionTimeMs", greaterThan(0))
.body("executionTimeMs", lessThan(5000)); // Moins de 5 secondes
}
@Test
@Order(12)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit retourner des statistiques complètes")
void testAdvancedSearchStatistics() {
MembreSearchCriteria criteria =
MembreSearchCriteria.builder()
.statut("ACTIF") // Ajouter un critère valide
.includeInactifs(true)
.build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("statistics", notNullValue())
.body("statistics.membresActifs", greaterThanOrEqualTo(0))
.body("statistics.membresInactifs", greaterThanOrEqualTo(0))
.body("statistics.ageMoyen", greaterThanOrEqualTo(0.0))
.body("statistics.ageMin", greaterThanOrEqualTo(0))
.body("statistics.ageMax", greaterThanOrEqualTo(0))
.body("statistics.nombreOrganisations", greaterThanOrEqualTo(0))
.body("statistics.ancienneteMoyenne", greaterThanOrEqualTo(0.0));
}
@Test
@Order(13)
@TestSecurity(
user = "superadmin@unionflow.com",
roles = {"SUPER_ADMIN"})
@DisplayName("POST /api/membres/search/advanced doit gérer les caractères spéciaux")
void testAdvancedSearchWithSpecialCharacters() {
MembreSearchCriteria criteria =
MembreSearchCriteria.builder().query("marie-josé").nom("o'connor").build();
given()
.contentType(ContentType.JSON)
.body(criteria)
.when()
.post(ADVANCED_SEARCH_ENDPOINT)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("membres", notNullValue());
}
}

View File

@@ -0,0 +1,292 @@
package dev.lions.unionflow.server.resource;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.io.ByteArrayOutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.*;
/**
* Tests d'intégration pour les endpoints import/export de MembreResource
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MembreResourceImportExportTest {
private static final String BASE_PATH = "/api/membres";
@Inject MembreRepository membreRepository;
@Inject OrganisationRepository organisationRepository;
private Organisation testOrganisation;
private List<Membre> testMembres;
@BeforeEach
@Transactional
void setupTestData() {
// Créer une organisation de test
testOrganisation =
Organisation.builder()
.nom("Organisation Test Import/Export")
.typeOrganisation("ASSOCIATION")
.statut("ACTIF")
.email("org-import-export-" + System.currentTimeMillis() + "@test.com")
.build();
testOrganisation.setDateCreation(LocalDateTime.now());
testOrganisation.setActif(true);
organisationRepository.persist(testOrganisation);
// Créer quelques membres de test
testMembres = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
Membre membre =
Membre.builder()
.numeroMembre("UF-TEST-IMPORT-" + i)
.nom("Nom" + i)
.prenom("Prenom" + i)
.email("membre" + i + "-import-" + System.currentTimeMillis() + "@test.com")
.telephone("+22170123456" + i)
.dateNaissance(LocalDate.of(1990 + i, 1, 1))
.dateAdhesion(LocalDate.of(2023, 1, 1))
.organisation(testOrganisation)
.build();
membre.setDateCreation(LocalDateTime.now());
membre.setActif(true);
membreRepository.persist(membre);
testMembres.add(membre);
}
}
@AfterEach
@Transactional
void cleanupTestData() {
if (testMembres != null) {
testMembres.forEach(
membre -> {
if (membre.getId() != null) {
Membre m = membreRepository.findById(membre.getId());
if (m != null) {
membreRepository.delete(m);
}
}
});
}
if (testOrganisation != null && testOrganisation.getId() != null) {
Organisation org = organisationRepository.findById(testOrganisation.getId());
if (org != null) {
organisationRepository.delete(org);
}
}
}
@Test
@Order(1)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/membres/import/modele doit retourner un fichier Excel template")
void testTelechargerModeleImport() {
given()
.when()
.get(BASE_PATH + "/import/modele")
.then()
.statusCode(200)
.contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.header("Content-Disposition", containsString("attachment"))
.header("Content-Disposition", containsString("modele_import_membres"));
}
@Test
@Order(2)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("POST /api/membres/import doit importer des membres depuis Excel")
void testImporterMembresExcel() throws Exception {
// Créer un fichier Excel de test
byte[] excelFile = createTestExcelFile();
given()
.contentType("multipart/form-data")
.multiPart("file", "test_import.xlsx", excelFile, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.formParam("organisationId", testOrganisation.getId().toString())
.formParam("typeMembreDefaut", "ACTIF")
.formParam("mettreAJourExistants", "false")
.formParam("ignorerErreurs", "false")
.when()
.post(BASE_PATH + "/import")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("lignesTraitees", greaterThan(0))
.body("erreurs", notNullValue());
}
@Test
@Order(3)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("POST /api/membres/import doit retourner 400 pour fichier vide")
void testImporterMembresFichierVide() {
// Envoyer un fichier vide (tableau de bytes vide)
byte[] emptyFile = new byte[0];
given()
.contentType("multipart/form-data")
.multiPart("file", "empty.xlsx", emptyFile, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.formParam("organisationId", testOrganisation.getId().toString())
.when()
.post(BASE_PATH + "/import")
.then()
.statusCode(400);
}
@Test
@Order(4)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/membres/export doit exporter des membres en Excel")
void testExporterMembresExcel() {
given()
.queryParam("format", "EXCEL")
.queryParam("statut", "ACTIF")
.when()
.get(BASE_PATH + "/export")
.then()
.statusCode(200)
.contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.header("Content-Disposition", containsString("attachment"));
}
@Test
@Order(5)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/membres/export doit exporter en CSV")
void testExporterMembresCSV() {
given()
.queryParam("format", "CSV")
.queryParam("statut", "ACTIF")
.when()
.get(BASE_PATH + "/export")
.then()
.statusCode(200)
.contentType("text/csv")
.header("Content-Disposition", containsString("attachment"));
}
@Test
@Order(6)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/membres/export/count doit retourner le nombre de membres à exporter")
void testCompterMembresPourExport() {
given()
.queryParam("statut", "ACTIF")
.when()
.get(BASE_PATH + "/export/count")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("count", greaterThanOrEqualTo(0));
}
@Test
@Order(7)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("POST /api/membres/export/selection doit exporter une sélection de membres")
void testExporterSelectionMembres() {
List<UUID> membreIds = new ArrayList<>();
testMembres.forEach(m -> membreIds.add(m.getId()));
given()
.contentType(ContentType.JSON)
.body(membreIds)
.queryParam("format", "EXCEL")
.when()
.post(BASE_PATH + "/export/selection")
.then()
.statusCode(200)
.contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.header("Content-Disposition", containsString("attachment"));
}
@Test
@Order(8)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/membres/export doit supporter inclureStatistiques")
void testExporterAvecStatistiques() {
given()
.queryParam("format", "EXCEL")
.queryParam("inclureStatistiques", "true")
.queryParam("statut", "ACTIF")
.when()
.get(BASE_PATH + "/export")
.then()
.statusCode(200)
.contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
@Test
@Order(9)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/membres/export doit supporter le chiffrement")
void testExporterAvecChiffrement() {
given()
.queryParam("format", "EXCEL")
.queryParam("motDePasse", "testPassword123")
.queryParam("statut", "ACTIF")
.when()
.get(BASE_PATH + "/export")
.then()
.statusCode(200)
.contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
/**
* Crée un fichier Excel de test avec des données de membres valides
*/
private byte[] createTestExcelFile() throws Exception {
try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("Membres");
// En-têtes
Row headerRow = sheet.createRow(0);
String[] headers = {
"nom", "prenom", "email", "telephone", "dateNaissance", "dateAdhesion"
};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
}
// Données de test
Row dataRow = sheet.createRow(1);
dataRow.createCell(0).setCellValue("TestNom");
dataRow.createCell(1).setCellValue("TestPrenom");
dataRow.createCell(2).setCellValue("test-import-" + System.currentTimeMillis() + "@test.com");
dataRow.createCell(3).setCellValue("+221701234999");
dataRow.createCell(4).setCellValue("1990-01-01");
dataRow.createCell(5).setCellValue("2023-01-01");
workbook.write(out);
return out.toByteArray();
}
}
}

View File

@@ -0,0 +1,351 @@
package dev.lions.unionflow.server.resource;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.*;
/**
* Tests d'intégration pour OrganisationResource
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrganisationResourceTest {
private static final String BASE_PATH = "/api/organisations";
@Inject OrganisationRepository organisationRepository;
private Organisation testOrganisation;
@BeforeEach
@Transactional
void setupTestData() {
// Créer une organisation de test
testOrganisation =
Organisation.builder()
.nom("Organisation Test")
.typeOrganisation("ASSOCIATION")
.statut("ACTIF")
.email("test-org-" + System.currentTimeMillis() + "@test.com")
.telephone("+221701234567")
.ville("Dakar")
.pays("Sénégal")
.build();
testOrganisation.setDateCreation(LocalDateTime.now());
testOrganisation.setActif(true);
organisationRepository.persist(testOrganisation);
}
@AfterEach
@Transactional
void cleanupTestData() {
if (testOrganisation != null && testOrganisation.getId() != null) {
Organisation orgToDelete = organisationRepository.findById(testOrganisation.getId());
if (orgToDelete != null) {
organisationRepository.delete(orgToDelete);
}
}
}
@Test
@Order(1)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/organisations doit retourner la liste des organisations")
void testListerOrganisations() {
given()
.when()
.get(BASE_PATH)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("$", notNullValue());
}
@Test
@Order(2)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/organisations/{id} doit retourner une organisation existante")
void testObtenirOrganisation() {
UUID orgId = testOrganisation.getId();
given()
.pathParam("id", orgId)
.when()
.get(BASE_PATH + "/{id}")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("id", equalTo(orgId.toString()))
.body("nom", equalTo("Organisation Test"));
}
@Test
@Order(3)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("GET /api/organisations/{id} doit retourner 404 pour un ID inexistant")
void testObtenirOrganisationInexistante() {
UUID fakeId = UUID.randomUUID();
given()
.pathParam("id", fakeId)
.when()
.get(BASE_PATH + "/{id}")
.then()
.statusCode(404);
}
@Test
@Order(4)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("POST /api/organisations doit créer une nouvelle organisation")
void testCreerOrganisation() {
OrganisationDTO newOrg = new OrganisationDTO();
newOrg.setNom("Nouvelle Organisation Test");
newOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION);
newOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE);
newOrg.setEmail("nouvelle-org-" + System.currentTimeMillis() + "@test.com");
newOrg.setTelephone("+221701234568");
newOrg.setVille("Thiès");
newOrg.setPays("Sénégal");
String location =
given()
.contentType(ContentType.JSON)
.body(newOrg)
.when()
.post(BASE_PATH)
.then()
.statusCode(201)
.contentType(ContentType.JSON)
.body("nom", equalTo("Nouvelle Organisation Test"))
.body("id", notNullValue())
.extract()
.header("Location");
// Nettoyer l'organisation créée
if (location != null && location.contains("/")) {
String idStr = location.substring(location.lastIndexOf("/") + 1);
try {
UUID createdId = UUID.fromString(idStr);
Organisation created = organisationRepository.findById(createdId);
if (created != null) {
organisationRepository.delete(created);
}
} catch (Exception e) {
// Ignorer les erreurs de nettoyage
}
}
}
@Test
@Order(5)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("POST /api/organisations doit retourner 400 pour données invalides")
void testCreerOrganisationInvalide() {
OrganisationDTO invalidOrg = new OrganisationDTO();
invalidOrg.setNom(""); // Nom vide - invalide
invalidOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION);
invalidOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE);
invalidOrg.setEmail("invalid-email"); // Email invalide
given()
.contentType(ContentType.JSON)
.body(invalidOrg)
.when()
.post(BASE_PATH)
.then()
.statusCode(400);
}
@Test
@Order(6)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("POST /api/organisations doit retourner 409 pour email dupliqué")
void testCreerOrganisationEmailDuplique() {
OrganisationDTO duplicateOrg = new OrganisationDTO();
duplicateOrg.setNom("Autre Organisation");
duplicateOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION);
duplicateOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE);
duplicateOrg.setEmail(testOrganisation.getEmail()); // Email déjà utilisé
given()
.contentType(ContentType.JSON)
.body(duplicateOrg)
.when()
.post(BASE_PATH)
.then()
.statusCode(409);
}
@Test
@Order(7)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("PUT /api/organisations/{id} doit mettre à jour une organisation")
void testModifierOrganisation() {
UUID orgId = testOrganisation.getId();
OrganisationDTO updatedOrg = new OrganisationDTO();
updatedOrg.setNom("Organisation Modifiée");
updatedOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION);
updatedOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE);
updatedOrg.setEmail(testOrganisation.getEmail());
updatedOrg.setTelephone("+221701234999");
updatedOrg.setVille("Saint-Louis");
updatedOrg.setPays("Sénégal");
given()
.contentType(ContentType.JSON)
.pathParam("id", orgId)
.body(updatedOrg)
.when()
.put(BASE_PATH + "/{id}")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("nom", equalTo("Organisation Modifiée"))
.body("telephone", equalTo("+221701234999"));
}
@Test
@Order(8)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("PUT /api/organisations/{id} doit retourner 404 pour ID inexistant")
void testModifierOrganisationInexistante() {
UUID fakeId = UUID.randomUUID();
OrganisationDTO updatedOrg = new OrganisationDTO();
updatedOrg.setNom("Organisation Test");
updatedOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION);
updatedOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE);
updatedOrg.setEmail("fake-" + System.currentTimeMillis() + "@test.com");
given()
.contentType(ContentType.JSON)
.pathParam("id", fakeId)
.body(updatedOrg)
.when()
.put(BASE_PATH + "/{id}")
.then()
.statusCode(404);
}
@Test
@Order(9)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("DELETE /api/organisations/{id} doit supprimer une organisation")
void testSupprimerOrganisation() {
// Créer une organisation temporaire pour la suppression
Organisation tempOrg =
Organisation.builder()
.nom("Organisation à Supprimer")
.typeOrganisation("ASSOCIATION")
.statut("ACTIF")
.email("temp-delete-" + System.currentTimeMillis() + "@test.com")
.build();
tempOrg.setDateCreation(LocalDateTime.now());
tempOrg.setActif(true);
organisationRepository.persist(tempOrg);
UUID tempId = tempOrg.getId();
given()
.pathParam("id", tempId)
.when()
.delete(BASE_PATH + "/{id}")
.then()
.statusCode(204);
// Vérifier que l'organisation a été supprimée
Organisation deleted = organisationRepository.findById(tempId);
assert deleted == null : "L'organisation devrait être supprimée";
}
@Test
@Order(10)
@TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
@DisplayName("DELETE /api/organisations/{id} doit retourner 404 pour ID inexistant")
void testSupprimerOrganisationInexistante() {
UUID fakeId = UUID.randomUUID();
given()
.pathParam("id", fakeId)
.when()
.delete(BASE_PATH + "/{id}")
.then()
.statusCode(404);
}
@Test
@Order(11)
@TestSecurity(user = "membre@unionflow.com", roles = {"MEMBRE"})
@DisplayName("GET /api/organisations doit être accessible aux membres")
void testListerOrganisationsPourMembre() {
given()
.when()
.get(BASE_PATH)
.then()
.statusCode(200);
}
@Test
@Order(12)
@DisplayName("GET /api/organisations doit être accessible publiquement")
void testListerOrganisationsPublic() {
given()
.when()
.get(BASE_PATH)
.then()
.statusCode(200);
}
@Test
@Order(13)
@TestSecurity(user = "membre@unionflow.com", roles = {"MEMBRE"})
@DisplayName("POST /api/organisations doit être accessible aux membres")
void testCreerOrganisationPourMembre() {
OrganisationDTO newOrg = new OrganisationDTO();
newOrg.setNom("Organisation Créée par Membre");
newOrg.setTypeOrganisation(dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation.ASSOCIATION);
newOrg.setStatut(dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation.ACTIVE);
newOrg.setEmail("membre-org-" + System.currentTimeMillis() + "@test.com");
String location =
given()
.contentType(ContentType.JSON)
.body(newOrg)
.when()
.post(BASE_PATH)
.then()
.statusCode(201)
.extract()
.header("Location");
// Nettoyer
if (location != null && location.contains("/")) {
String idStr = location.substring(location.lastIndexOf("/") + 1);
try {
UUID createdId = UUID.fromString(idStr);
Organisation created = organisationRepository.findById(createdId);
if (created != null) {
organisationRepository.delete(created);
}
} catch (Exception e) {
// Ignorer
}
}
}
}

View File

@@ -0,0 +1,369 @@
package dev.lions.unionflow.server.service;
import static org.assertj.core.api.Assertions.*;
import dev.lions.unionflow.server.api.dto.membre.MembreDTO;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.*;
/**
* Tests unitaires pour MembreImportExportService
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MembreImportExportServiceTest {
@Inject MembreImportExportService importExportService;
@Inject MembreRepository membreRepository;
@Inject OrganisationRepository organisationRepository;
@Inject MembreService membreService;
private Organisation testOrganisation;
private List<Membre> testMembres;
@BeforeEach
@Transactional
void setupTestData() {
// Créer une organisation de test
testOrganisation =
Organisation.builder()
.nom("Organisation Test Import/Export Service")
.typeOrganisation("ASSOCIATION")
.statut("ACTIF")
.email("org-service-" + System.currentTimeMillis() + "@test.com")
.build();
testOrganisation.setDateCreation(LocalDateTime.now());
testOrganisation.setActif(true);
organisationRepository.persist(testOrganisation);
// Créer quelques membres de test
testMembres = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
Membre membre =
Membre.builder()
.numeroMembre("UF-SERVICE-TEST-" + i)
.nom("NomService" + i)
.prenom("PrenomService" + i)
.email("service" + i + "-" + System.currentTimeMillis() + "@test.com")
.telephone("+2217012345" + (10 + i))
.dateNaissance(LocalDate.of(1985 + i, 1, 1))
.dateAdhesion(LocalDate.of(2022, 1, 1))
.organisation(testOrganisation)
.build();
membre.setDateCreation(LocalDateTime.now());
membre.setActif(true);
membreRepository.persist(membre);
testMembres.add(membre);
}
}
@AfterEach
@Transactional
void cleanupTestData() {
if (testMembres != null) {
testMembres.forEach(
membre -> {
if (membre.getId() != null) {
Membre m = membreRepository.findById(membre.getId());
if (m != null) {
membreRepository.delete(m);
}
}
});
}
if (testOrganisation != null && testOrganisation.getId() != null) {
Organisation org = organisationRepository.findById(testOrganisation.getId());
if (org != null) {
organisationRepository.delete(org);
}
}
}
@Test
@Order(1)
@DisplayName("Doit générer un modèle d'import Excel valide")
void testGenererModeleImport() throws Exception {
// When
byte[] modele = importExportService.genererModeleImport();
// Then
assertThat(modele).isNotNull();
assertThat(modele.length).isGreaterThan(0);
// Vérifier que c'est un fichier Excel valide
try (Workbook workbook = new XSSFWorkbook(new ByteArrayInputStream(modele))) {
Sheet sheet = workbook.getSheetAt(0);
assertThat(sheet).isNotNull();
Row headerRow = sheet.getRow(0);
assertThat(headerRow).isNotNull();
// Vérifier la présence de colonnes essentielles
boolean hasNom = false, hasPrenom = false, hasEmail = false;
for (Cell cell : headerRow) {
String value = cell.getStringCellValue().toLowerCase();
if (value.contains("nom")) hasNom = true;
if (value.contains("prenom")) hasPrenom = true;
if (value.contains("email")) hasEmail = true;
}
assertThat(hasNom).isTrue();
assertThat(hasPrenom).isTrue();
assertThat(hasEmail).isTrue();
}
}
@Test
@Order(2)
@DisplayName("Doit importer des membres depuis un fichier Excel valide")
void testImporterDepuisExcel() throws Exception {
// Given - Créer un fichier Excel de test
byte[] excelFile = createValidExcelFile();
ByteArrayInputStream inputStream = new ByteArrayInputStream(excelFile);
// When
MembreImportExportService.ResultatImport resultat =
importExportService.importerMembres(
inputStream,
"test_import.xlsx",
testOrganisation.getId(),
"ACTIF",
false,
false);
// Then
assertThat(resultat).isNotNull();
assertThat(resultat.lignesTraitees).isGreaterThan(0);
assertThat(resultat.membresImportes).isNotEmpty();
assertThat(resultat.erreurs).isEmpty();
}
@Test
@Order(3)
@DisplayName("Doit gérer les erreurs lors de l'import Excel")
void testImporterExcelAvecErreurs() throws Exception {
// Given - Créer un fichier Excel avec des données invalides
byte[] excelFile = createInvalidExcelFile();
ByteArrayInputStream inputStream = new ByteArrayInputStream(excelFile);
// When
MembreImportExportService.ResultatImport resultat =
importExportService.importerMembres(
inputStream,
"test_invalid.xlsx",
testOrganisation.getId(),
"ACTIF",
false,
true); // Ignorer les erreurs
// Then
assertThat(resultat).isNotNull();
assertThat(resultat.erreurs).isNotEmpty();
}
@Test
@Order(4)
@DisplayName("Doit exporter des membres vers Excel")
void testExporterVersExcel() throws Exception {
// Given - Convertir les membres de test en DTOs
List<MembreDTO> membresDTO = new ArrayList<>();
testMembres.forEach(m -> membresDTO.add(membreService.convertToDTO(m)));
// When
byte[] excelData =
importExportService.exporterVersExcel(
membresDTO,
List.of("nom", "prenom", "email", "telephone"),
true, // inclureHeaders
false, // formaterDates
false, // inclureStatistiques
null); // motDePasse
// Then
assertThat(excelData).isNotNull();
assertThat(excelData.length).isGreaterThan(0);
// Vérifier que c'est un fichier Excel valide
try (Workbook workbook = new XSSFWorkbook(new ByteArrayInputStream(excelData))) {
Sheet sheet = workbook.getSheetAt(0);
assertThat(sheet).isNotNull();
assertThat(sheet.getLastRowNum()).isGreaterThan(0);
}
}
@Test
@Order(5)
@DisplayName("Doit exporter des membres vers Excel avec statistiques")
void testExporterVersExcelAvecStatistiques() throws Exception {
// Given
List<MembreDTO> membresDTO = new ArrayList<>();
testMembres.forEach(m -> membresDTO.add(membreService.convertToDTO(m)));
// When
byte[] excelData =
importExportService.exporterVersExcel(
membresDTO,
List.of("nom", "prenom", "email"),
true, // inclureHeaders
false, // formaterDates
true, // inclureStatistiques
null); // motDePasse
// Then
assertThat(excelData).isNotNull();
// Vérifier qu'il y a plusieurs feuilles (données + statistiques)
try (Workbook workbook = new XSSFWorkbook(new ByteArrayInputStream(excelData))) {
assertThat(workbook.getNumberOfSheets()).isGreaterThan(1);
Sheet statsSheet = workbook.getSheet("Statistiques");
assertThat(statsSheet).isNotNull();
}
}
@Test
@Order(6)
@DisplayName("Doit exporter des membres vers Excel avec chiffrement")
void testExporterVersExcelAvecChiffrement() throws Exception {
// Given
List<MembreDTO> membresDTO = new ArrayList<>();
testMembres.forEach(m -> membresDTO.add(membreService.convertToDTO(m)));
// When
byte[] excelData =
importExportService.exporterVersExcel(
membresDTO,
List.of("nom", "prenom", "email"),
true, // inclureHeaders
false, // formaterDates
false, // inclureStatistiques
"testPassword123"); // motDePasse
// Then
assertThat(excelData).isNotNull();
// Note: La vérification du chiffrement nécessiterait d'essayer d'ouvrir le fichier avec le mot de passe
}
@Test
@Order(7)
@DisplayName("Doit exporter des membres vers CSV")
void testExporterVersCSV() throws Exception {
// Given
List<MembreDTO> membresDTO = new ArrayList<>();
testMembres.forEach(m -> membresDTO.add(membreService.convertToDTO(m)));
// When - Utiliser les groupes de colonnes attendus par la méthode
byte[] csvData =
importExportService.exporterVersCSV(
membresDTO,
List.of("PERSO", "CONTACT"), // Groupes de colonnes
true, // inclureHeaders
false); // formaterDates
// Then
assertThat(csvData).isNotNull();
assertThat(csvData.length).isGreaterThan(0);
// Vérifier le contenu CSV - les en-têtes sont en français avec majuscules
String csvContent = new String(csvData, java.nio.charset.StandardCharsets.UTF_8);
assertThat(csvContent).contains("Nom");
assertThat(csvContent).contains("Prénom");
assertThat(csvContent).contains("Email");
}
@Test
@Order(8)
@DisplayName("Doit rejeter un format de fichier non supporté")
void testFormatNonSupporte() {
// Given
byte[] invalidFile = "Ceci n'est pas un fichier Excel".getBytes();
ByteArrayInputStream inputStream = new ByteArrayInputStream(invalidFile);
// When & Then
assertThatThrownBy(
() ->
importExportService.importerMembres(
inputStream,
"test.txt",
testOrganisation.getId(),
"ACTIF",
false,
false))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Format de fichier non supporté");
}
/**
* Crée un fichier Excel valide pour les tests d'import
*/
private byte[] createValidExcelFile() throws Exception {
try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("Membres");
// En-têtes
Row headerRow = sheet.createRow(0);
String[] headers = {
"nom", "prenom", "email", "telephone", "dateNaissance", "dateAdhesion"
};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
}
// Données valides
Row dataRow = sheet.createRow(1);
dataRow.createCell(0).setCellValue("TestNom");
dataRow.createCell(1).setCellValue("TestPrenom");
dataRow.createCell(2).setCellValue("test-valid-" + System.currentTimeMillis() + "@test.com");
dataRow.createCell(3).setCellValue("+221701234999");
dataRow.createCell(4).setCellValue("1990-01-01");
dataRow.createCell(5).setCellValue("2023-01-01");
workbook.write(out);
return out.toByteArray();
}
}
/**
* Crée un fichier Excel avec des données invalides pour tester la gestion d'erreurs
*/
private byte[] createInvalidExcelFile() throws Exception {
try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("Membres");
// En-têtes
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("nom");
headerRow.createCell(1).setCellValue("prenom");
headerRow.createCell(2).setCellValue("email");
// Données invalides (email manquant)
Row dataRow = sheet.createRow(1);
dataRow.createCell(0).setCellValue("TestNom");
dataRow.createCell(1).setCellValue("TestPrenom");
// Email manquant - devrait générer une erreur
workbook.write(out);
return out.toByteArray();
}
}
}

View File

@@ -0,0 +1,400 @@
package dev.lions.unionflow.server.service;
import static org.assertj.core.api.Assertions.*;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.*;
/**
* Tests pour la recherche avancée de membres
*
* @author UnionFlow Team
* @version 1.0
* @since 2025-01-19
*/
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MembreServiceAdvancedSearchTest {
@Inject MembreService membreService;
@Inject MembreRepository membreRepository;
@Inject OrganisationRepository organisationRepository;
private Organisation testOrganisation;
private List<Membre> testMembres;
@BeforeEach
@Transactional
void setupTestData() {
// Créer et persister une organisation de test
testOrganisation =
Organisation.builder()
.nom("Organisation Test")
.typeOrganisation("ASSOCIATION")
.statut("ACTIF")
.email("test@organisation.com")
.build();
testOrganisation.setDateCreation(LocalDateTime.now());
testOrganisation.setActif(true);
organisationRepository.persist(testOrganisation);
// Créer des membres de test avec différents profils
testMembres =
List.of(
// Membre actif jeune
createMembre("UF-2025-TEST001", "Dupont", "Marie", "marie.dupont@test.com",
"+221701234567", LocalDate.of(1995, 5, 15), LocalDate.of(2023, 1, 15),
"MEMBRE,SECRETAIRE", true),
// Membre actif âgé
createMembre("UF-2025-TEST002", "Martin", "Jean", "jean.martin@test.com",
"+221701234568", LocalDate.of(1970, 8, 20), LocalDate.of(2020, 3, 10),
"MEMBRE,PRESIDENT", true),
// Membre inactif
createMembre("UF-2025-TEST003", "Diallo", "Fatou", "fatou.diallo@test.com",
"+221701234569", LocalDate.of(1985, 12, 3), LocalDate.of(2021, 6, 5),
"MEMBRE", false),
// Membre avec email spécifique
createMembre("UF-2025-TEST004", "Sow", "Amadou", "amadou.sow@unionflow.com",
"+221701234570", LocalDate.of(1988, 3, 12), LocalDate.of(2022, 9, 20),
"MEMBRE,TRESORIER", true));
// Persister tous les membres
testMembres.forEach(membre -> membreRepository.persist(membre));
}
private Membre createMembre(String numero, String nom, String prenom, String email,
String telephone, LocalDate dateNaissance, LocalDate dateAdhesion,
String roles, boolean actif) {
Membre membre = Membre.builder()
.numeroMembre(numero)
.nom(nom)
.prenom(prenom)
.email(email)
.telephone(telephone)
.dateNaissance(dateNaissance)
.dateAdhesion(dateAdhesion)
.organisation(testOrganisation)
.build();
membre.setDateCreation(LocalDateTime.now());
membre.setActif(actif);
// Note: Le champ roles est maintenant List<MembreRole> et doit être géré via la relation MembreRole
// Pour les tests, on laisse la liste vide par défaut
return membre;
}
@AfterEach
@Transactional
void cleanupTestData() {
// Nettoyer les données de test
if (testMembres != null) {
testMembres.forEach(membre -> {
if (membre.getId() != null) {
// Recharger l'entité depuis la base pour éviter l'erreur "detached entity"
membreRepository.findByIdOptional(membre.getId()).ifPresent(m -> {
// Utiliser deleteById pour éviter les problèmes avec les entités détachées
membreRepository.deleteById(m.getId());
});
}
});
}
if (testOrganisation != null && testOrganisation.getId() != null) {
// Recharger l'entité depuis la base pour éviter l'erreur "detached entity"
organisationRepository.findByIdOptional(testOrganisation.getId()).ifPresent(o -> {
// Utiliser deleteById pour éviter les problèmes avec les entités détachées
organisationRepository.deleteById(o.getId());
});
}
}
@Test
@Order(1)
@DisplayName("Doit effectuer une recherche par terme général")
void testSearchByGeneralQuery() {
// Given
MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("marie").build();
// When
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isEqualTo(1);
assertThat(result.getMembres()).hasSize(1);
assertThat(result.getMembres().get(0).getPrenom()).isEqualToIgnoringCase("Marie");
assertThat(result.isFirst()).isTrue();
assertThat(result.isLast()).isTrue();
}
@Test
@Order(2)
@DisplayName("Doit filtrer par statut actif")
void testSearchByActiveStatus() {
// Given
MembreSearchCriteria criteria = MembreSearchCriteria.builder().statut("ACTIF").build();
// When
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isEqualTo(3); // 3 membres actifs
assertThat(result.getMembres()).hasSize(3);
assertThat(result.getMembres()).allMatch(membre -> "ACTIF".equals(membre.getStatut()));
}
@Test
@Order(3)
@DisplayName("Doit filtrer par tranche d'âge")
void testSearchByAgeRange() {
// Given
MembreSearchCriteria criteria = MembreSearchCriteria.builder().ageMin(25).ageMax(35).build();
// When
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isGreaterThan(0);
// Vérifier que tous les membres sont dans la tranche d'âge
result
.getMembres()
.forEach(
membre -> {
if (membre.getDateNaissance() != null) {
int age = LocalDate.now().getYear() - membre.getDateNaissance().getYear();
assertThat(age).isBetween(25, 35);
}
});
}
@Test
@Order(4)
@DisplayName("Doit filtrer par période d'adhésion")
void testSearchByAdhesionPeriod() {
// Given
MembreSearchCriteria criteria =
MembreSearchCriteria.builder()
.dateAdhesionMin(LocalDate.of(2022, 1, 1))
.dateAdhesionMax(LocalDate.of(2023, 12, 31))
.build();
// When
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("dateAdhesion"));
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isGreaterThan(0);
// Vérifier que toutes les dates d'adhésion sont dans la période
result
.getMembres()
.forEach(
membre -> {
if (membre.getDateAdhesion() != null) {
assertThat(membre.getDateAdhesion())
.isAfterOrEqualTo(LocalDate.of(2022, 1, 1))
.isBeforeOrEqualTo(LocalDate.of(2023, 12, 31));
}
});
}
@Test
@Order(5)
@DisplayName("Doit rechercher par email avec domaine spécifique")
void testSearchByEmailDomain() {
// Given
MembreSearchCriteria criteria = MembreSearchCriteria.builder().email("@unionflow.com").build();
// When
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isEqualTo(1);
assertThat(result.getMembres()).hasSize(1);
assertThat(result.getMembres().get(0).getEmail()).contains("@unionflow.com");
}
@Test
@Order(6)
@DisplayName("Doit filtrer par rôles")
void testSearchByRoles() {
// Given
MembreSearchCriteria criteria =
MembreSearchCriteria.builder().roles(List.of("PRESIDENT", "SECRETAIRE")).build();
// When
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isGreaterThan(0);
// Vérifier que tous les membres ont au moins un des rôles recherchés
result
.getMembres()
.forEach(
membre -> {
assertThat(membre.getRole())
.satisfiesAnyOf(
role -> assertThat(role).contains("PRESIDENT"),
role -> assertThat(role).contains("SECRETAIRE"));
});
}
@Test
@Order(7)
@DisplayName("Doit gérer la pagination correctement")
void testPagination() {
// Given
MembreSearchCriteria criteria =
MembreSearchCriteria.builder()
.includeInactifs(true) // Inclure tous les membres
.build();
// When - Première page
MembreSearchResultDTO firstPage =
membreService.searchMembresAdvanced(criteria, Page.of(0, 2), Sort.by("nom"));
// Then
assertThat(firstPage).isNotNull();
assertThat(firstPage.getCurrentPage()).isEqualTo(0);
assertThat(firstPage.getPageSize()).isEqualTo(2);
assertThat(firstPage.getMembres()).hasSizeLessThanOrEqualTo(2);
assertThat(firstPage.isFirst()).isTrue();
if (firstPage.getTotalElements() > 2) {
assertThat(firstPage.isLast()).isFalse();
assertThat(firstPage.isHasNext()).isTrue();
}
}
@Test
@Order(8)
@DisplayName("Doit calculer les statistiques correctement")
void testStatisticsCalculation() {
// Given
MembreSearchCriteria criteria =
MembreSearchCriteria.builder()
.includeInactifs(true) // Inclure tous les membres
.build();
// When
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));
// Then
assertThat(result).isNotNull();
assertThat(result.getStatistics()).isNotNull();
MembreSearchResultDTO.SearchStatistics stats = result.getStatistics();
assertThat(stats.getMembresActifs()).isEqualTo(3);
assertThat(stats.getMembresInactifs()).isEqualTo(1);
assertThat(stats.getAgeMoyen()).isGreaterThan(0);
assertThat(stats.getAgeMin()).isGreaterThan(0);
assertThat(stats.getAgeMax()).isGreaterThan(stats.getAgeMin());
assertThat(stats.getAncienneteMoyenne()).isGreaterThanOrEqualTo(0);
}
@Test
@Order(9)
@DisplayName("Doit retourner un résultat vide pour critères impossibles")
void testEmptyResultForImpossibleCriteria() {
// Given
MembreSearchCriteria criteria =
MembreSearchCriteria.builder().query("membre_inexistant_xyz").build();
// When
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isEqualTo(0);
assertThat(result.getMembres()).isEmpty();
assertThat(result.isEmpty()).isTrue();
assertThat(result.getTotalPages()).isEqualTo(0);
}
@Test
@Order(10)
@DisplayName("Doit valider la cohérence des critères")
void testCriteriaValidation() {
// Given - Critères incohérents
MembreSearchCriteria invalidCriteria =
MembreSearchCriteria.builder()
.ageMin(50)
.ageMax(30) // Âge max < âge min
.build();
// When & Then
assertThat(invalidCriteria.isValid()).isFalse();
}
@Test
@Order(11)
@DisplayName("Doit avoir des performances acceptables (< 500ms)")
void testSearchPerformance() {
// Given
MembreSearchCriteria criteria = MembreSearchCriteria.builder().includeInactifs(true).build();
// When & Then - Mesurer le temps d'exécution
long startTime = System.currentTimeMillis();
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 20), Sort.by("nom"));
long executionTime = System.currentTimeMillis() - startTime;
// Vérifications
assertThat(result).isNotNull();
assertThat(executionTime).isLessThan(500L); // Moins de 500ms
// Log pour monitoring
System.out.printf(
"Recherche avancée exécutée en %d ms pour %d résultats%n",
executionTime, result.getTotalElements());
}
@Test
@Order(12)
@DisplayName("Doit gérer les critères avec caractères spéciaux")
void testSearchWithSpecialCharacters() {
// Given
MembreSearchCriteria criteria =
MembreSearchCriteria.builder().query("marie-josé").nom("o'connor").build();
// When
MembreSearchResultDTO result =
membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));
// Then
assertThat(result).isNotNull();
// La recherche ne doit pas échouer même avec des caractères spéciaux
assertThat(result.getTotalElements()).isGreaterThanOrEqualTo(0);
}
}