Alignement design systeme OK

This commit is contained in:
DahoudG
2025-09-20 03:56:11 +00:00
parent a1214bc116
commit 96a17eadbd
34 changed files with 11720 additions and 766 deletions

View File

@@ -0,0 +1,320 @@
package dev.lions.unionflow.server.resource;
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 org.junit.jupiter.api.*;
import java.time.LocalDate;
import java.util.List;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
/**
* 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()
.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()
.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,409 @@
package dev.lions.unionflow.server.service;
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 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 org.junit.jupiter.api.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
/**
* 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;
private static Organisation testOrganisation;
private static List<Membre> testMembres;
@BeforeAll
@Transactional
static void setupTestData() {
// Créer une organisation de test
testOrganisation = Organisation.builder()
.nom("Organisation Test")
.typeOrganisation("ASSOCIATION")
.statut("ACTIF")
.actif(true)
.dateCreation(LocalDateTime.now())
.build();
testOrganisation.persist();
// Créer des membres de test avec différents profils
testMembres = List.of(
// Membre actif jeune
Membre.builder()
.numeroMembre("UF-2025-TEST001")
.nom("Dupont")
.prenom("Marie")
.email("marie.dupont@test.com")
.telephone("+221701234567")
.dateNaissance(LocalDate.of(1995, 5, 15))
.dateAdhesion(LocalDate.of(2023, 1, 15))
.roles("MEMBRE,SECRETAIRE")
.actif(true)
.organisation(testOrganisation)
.dateCreation(LocalDateTime.now())
.build(),
// Membre actif âgé
Membre.builder()
.numeroMembre("UF-2025-TEST002")
.nom("Martin")
.prenom("Jean")
.email("jean.martin@test.com")
.telephone("+221701234568")
.dateNaissance(LocalDate.of(1970, 8, 20))
.dateAdhesion(LocalDate.of(2020, 3, 10))
.roles("MEMBRE,PRESIDENT")
.actif(true)
.organisation(testOrganisation)
.dateCreation(LocalDateTime.now())
.build(),
// Membre inactif
Membre.builder()
.numeroMembre("UF-2025-TEST003")
.nom("Diallo")
.prenom("Fatou")
.email("fatou.diallo@test.com")
.telephone("+221701234569")
.dateNaissance(LocalDate.of(1985, 12, 3))
.dateAdhesion(LocalDate.of(2021, 6, 5))
.roles("MEMBRE")
.actif(false)
.organisation(testOrganisation)
.dateCreation(LocalDateTime.now())
.build(),
// Membre avec email spécifique
Membre.builder()
.numeroMembre("UF-2025-TEST004")
.nom("Sow")
.prenom("Amadou")
.email("amadou.sow@unionflow.com")
.telephone("+221701234570")
.dateNaissance(LocalDate.of(1988, 3, 12))
.dateAdhesion(LocalDate.of(2022, 9, 20))
.roles("MEMBRE,TRESORIER")
.actif(true)
.organisation(testOrganisation)
.dateCreation(LocalDateTime.now())
.build()
);
// Persister tous les membres
testMembres.forEach(membre -> membre.persist());
}
@AfterAll
@Transactional
static void cleanupTestData() {
// Nettoyer les données de test
if (testMembres != null) {
testMembres.forEach(membre -> {
if (membre.isPersistent()) {
membre.delete();
}
});
}
if (testOrganisation != null && testOrganisation.isPersistent()) {
testOrganisation.delete();
}
}
@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);
}
}