Refactoring - Version stable
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package dev.lions.unionflow.server;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests de couverture pour UnionFlowServerApplication.buildBaseUrl().
|
||||
* Utilise des sous-classes pour contrôler getUnionflowDomain() sans manipulation d'env.
|
||||
*/
|
||||
@DisplayName("UnionFlowServerApplication.buildBaseUrl() - branches manquantes")
|
||||
class UnionFlowServerApplicationBranchTest {
|
||||
|
||||
/** Sous-classe qui permet de simuler une valeur spécifique de UNIONFLOW_DOMAIN. */
|
||||
@jakarta.enterprise.inject.Vetoed
|
||||
private static class TestableApp extends UnionFlowServerApplication {
|
||||
private final String domain;
|
||||
|
||||
TestableApp(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getUnionflowDomain() {
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
private UnionFlowServerApplication buildApp(String activeProfile, String httpHost, int httpPort, String domain)
|
||||
throws Exception {
|
||||
UnionFlowServerApplication app = new TestableApp(domain);
|
||||
setField(app, "activeProfile", activeProfile);
|
||||
setField(app, "httpHost", httpHost);
|
||||
setField(app, "httpPort", httpPort);
|
||||
setField(app, "applicationName", "unionflow-server");
|
||||
setField(app, "applicationVersion", "3.0.0");
|
||||
setField(app, "quarkusVersion", "3.15.1");
|
||||
return app;
|
||||
}
|
||||
|
||||
private void setField(Object target, String fieldName, Object value) throws Exception {
|
||||
Field field = UnionFlowServerApplication.class.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
// ── Branch : prod + UNIONFLOW_DOMAIN null → domain == null → false → localhost ──
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: profil prod, domain=null → condition false (A=false) → URL localhost")
|
||||
void buildBaseUrl_prodProfile_domainNull_returnsLocalhost() throws Exception {
|
||||
UnionFlowServerApplication app = buildApp("prod", "0.0.0.0", 8085, null);
|
||||
String url = app.buildBaseUrl();
|
||||
// domain == null → A (domain != null) = false → condition false → localhost
|
||||
assertThat(url).isEqualTo("http://localhost:8085");
|
||||
}
|
||||
|
||||
// ── Branch : prod + UNIONFLOW_DOMAIN = "" → domain.isEmpty() = true → false → localhost ──
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: profil prod, domain='' → condition false (B=false) → URL localhost")
|
||||
void buildBaseUrl_prodProfile_emptyDomain_returnsLocalhost() throws Exception {
|
||||
UnionFlowServerApplication app = buildApp("prod", "0.0.0.0", 8085, "");
|
||||
String url = app.buildBaseUrl();
|
||||
// domain != null (A=true) && !domain.isEmpty() = false (B=false) → false → localhost
|
||||
assertThat(url).isEqualTo("http://localhost:8085");
|
||||
}
|
||||
|
||||
// ── Branch : prod + UNIONFLOW_DOMAIN = "api.example.com" → return https://domain ──
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: profil prod, domain non-vide → retourne https://domain")
|
||||
void buildBaseUrl_prodProfile_domainSet_returnsHttpsDomain() throws Exception {
|
||||
UnionFlowServerApplication app = buildApp("prod", "0.0.0.0", 8085, "api.example.com");
|
||||
String url = app.buildBaseUrl();
|
||||
// domain != null (A=true) && !domain.isEmpty() (B=true) → true → https://domain
|
||||
assertThat(url).isEqualTo("https://api.example.com");
|
||||
}
|
||||
|
||||
// ── Branch : httpHost != "0.0.0.0" → utilise httpHost directement ──
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: httpHost != 0.0.0.0 → utilise httpHost directement")
|
||||
void buildBaseUrl_customHost_usesHostDirectly() throws Exception {
|
||||
UnionFlowServerApplication app = buildApp("dev", "192.168.1.10", 8085, null);
|
||||
String url = app.buildBaseUrl();
|
||||
assertThat(url).isEqualTo("http://192.168.1.10:8085");
|
||||
}
|
||||
|
||||
// ── Branch : httpHost == "0.0.0.0" → localhost ──
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: httpHost=0.0.0.0 → localhost")
|
||||
void buildBaseUrl_defaultHost_returnsLocalhost() throws Exception {
|
||||
UnionFlowServerApplication app = buildApp("dev", "0.0.0.0", 8080, null);
|
||||
String url = app.buildBaseUrl();
|
||||
assertThat(url).isEqualTo("http://localhost:8080");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package dev.lions.unionflow.server;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests complémentaires pour {@link UnionFlowServerApplication#buildBaseUrl()}.
|
||||
*
|
||||
* <p>Utilise une sous-classe {@link TestableApp} pour contrôler la valeur de
|
||||
* UNIONFLOW_DOMAIN sans manipulation fragile des variables d'environnement.
|
||||
*/
|
||||
@DisplayName("UnionFlowServerApplication — buildBaseUrl")
|
||||
class UnionFlowServerApplicationBuildBaseUrlTest {
|
||||
|
||||
/** Sous-classe permettant de simuler une valeur de UNIONFLOW_DOMAIN sans toucher à l'env. */
|
||||
@jakarta.enterprise.inject.Vetoed
|
||||
private static class TestableApp extends UnionFlowServerApplication {
|
||||
private final String mockDomain;
|
||||
|
||||
TestableApp(String mockDomain) {
|
||||
this.mockDomain = mockDomain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getUnionflowDomain() {
|
||||
return mockDomain;
|
||||
}
|
||||
}
|
||||
|
||||
/** Utilitaire : injecte une valeur dans un champ privé de l'instance via réflexion. */
|
||||
private static void setField(Object instance, String fieldName, Object value) throws Exception {
|
||||
Field f = UnionFlowServerApplication.class.getDeclaredField(fieldName);
|
||||
f.setAccessible(true);
|
||||
f.set(instance, value);
|
||||
}
|
||||
|
||||
private static UnionFlowServerApplication buildApp(
|
||||
String activeProfile, String httpHost, int httpPort, String domain) throws Exception {
|
||||
UnionFlowServerApplication app = new TestableApp(domain);
|
||||
setField(app, "activeProfile", activeProfile);
|
||||
setField(app, "httpHost", httpHost);
|
||||
setField(app, "httpPort", httpPort);
|
||||
return app;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: profil dev → URL http://localhost:port (branche profil non-prod)")
|
||||
void buildBaseUrl_profilDev_retourneUrlLocale() throws Exception {
|
||||
UnionFlowServerApplication app = buildApp("dev", "0.0.0.0", 8085, null);
|
||||
assertThat(app.buildBaseUrl()).isEqualTo("http://localhost:8085");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: profil test → URL http://localhost:port (branche profil non-prod)")
|
||||
void buildBaseUrl_profilTest_retourneUrlLocale() throws Exception {
|
||||
UnionFlowServerApplication app = buildApp("test", "0.0.0.0", 9090, null);
|
||||
assertThat(app.buildBaseUrl()).isEqualTo("http://localhost:9090");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: httpHost non '0.0.0.0' → utilise httpHost directement (branche ternaire)")
|
||||
void buildBaseUrl_httpHostPersonnalise_utilisehttpHostDirectement() throws Exception {
|
||||
UnionFlowServerApplication app = buildApp("dev", "192.168.1.100", 8085, null);
|
||||
// httpHost != "0.0.0.0" → host = "192.168.1.100" (branche else du ternaire)
|
||||
assertThat(app.buildBaseUrl()).isEqualTo("http://192.168.1.100:8085");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: profil prod, domain=null → condition false (A=false) → URL locale")
|
||||
void buildBaseUrl_profilProd_sansDomain_retourneUrlLocale() throws Exception {
|
||||
// getUnionflowDomain() retourne null → domain != null → false → localhost
|
||||
UnionFlowServerApplication app = buildApp("prod", "0.0.0.0", 8085, null);
|
||||
assertThat(app.buildBaseUrl()).isEqualTo("http://localhost:8085");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: profil prod avec UNIONFLOW_DOMAIN défini → return 'https://domain'")
|
||||
void buildBaseUrl_profilProd_avecDomain_retourneHttps() throws Exception {
|
||||
// getUnionflowDomain() retourne "api.lions.dev" → condition true → https://
|
||||
UnionFlowServerApplication app = buildApp("prod", "0.0.0.0", 8085, "api.lions.dev");
|
||||
assertThat(app.buildBaseUrl()).isEqualTo("https://api.lions.dev");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: profil prod avec UNIONFLOW_DOMAIN vide ('') → URL locale (branche isEmpty()=true)")
|
||||
void buildBaseUrl_profilProd_domainVide_retourneUrlLocale() throws Exception {
|
||||
// getUnionflowDomain() retourne "" → domain != null (A=true) && !domain.isEmpty() (B=false) → false
|
||||
UnionFlowServerApplication app = buildApp("prod", "0.0.0.0", 8085, "");
|
||||
assertThat(app.buildBaseUrl()).isEqualTo("http://localhost:8085");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package dev.lions.unionflow.server;
|
||||
|
||||
import io.quarkus.runtime.Quarkus;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
/**
|
||||
* Tests pour UnionFlowServerApplication.main() et run() sans @QuarkusTest.
|
||||
* Utilise le mode inline de Mockito (activé via mockito-extensions) pour
|
||||
* moquer les méthodes statiques de Quarkus sans bloquer le test.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("UnionFlowServerApplication — main() et run() (mock inline)")
|
||||
class UnionFlowServerApplicationStaticTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("main() s'exécute sans exception avec Quarkus.run mocké")
|
||||
void main_avecQuarkusRunMocke() {
|
||||
try (MockedStatic<Quarkus> mockedQuarkus = Mockito.mockStatic(Quarkus.class)) {
|
||||
mockedQuarkus.when(() -> Quarkus.run(Mockito.any(), Mockito.<String[]>any()))
|
||||
.thenAnswer(invocation -> null);
|
||||
assertThatCode(() -> UnionFlowServerApplication.main())
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("run() retourne 0 avec Quarkus.waitForExit mocké")
|
||||
void run_retourneZeroAvecWaitForExitMocke() throws Exception {
|
||||
try (MockedStatic<Quarkus> mockedQuarkus = Mockito.mockStatic(Quarkus.class)) {
|
||||
mockedQuarkus.when(Quarkus::waitForExit).thenAnswer(invocation -> null);
|
||||
UnionFlowServerApplication app = new UnionFlowServerApplication();
|
||||
int result = app.run();
|
||||
assertThat(result).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
/** Utilitaire : set un champ privé sur une instance directe (pas un proxy CDI). */
|
||||
private static void setField(Object instance, String fieldName, Object value) throws Exception {
|
||||
Field f = UnionFlowServerApplication.class.getDeclaredField(fieldName);
|
||||
f.setAccessible(true);
|
||||
f.set(instance, value);
|
||||
}
|
||||
|
||||
private static String callBuildBaseUrl(UnionFlowServerApplication app) throws Exception {
|
||||
Method m = UnionFlowServerApplication.class.getDeclaredMethod("buildBaseUrl");
|
||||
m.setAccessible(true);
|
||||
return (String) m.invoke(app);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl: profil prod sans UNIONFLOW_DOMAIN → http URL")
|
||||
void buildBaseUrl_prodProfile_domainNull_returnsHttp() throws Exception {
|
||||
// Instance directe (pas CDI proxy) → la réflexion fonctionne correctement
|
||||
UnionFlowServerApplication app = new UnionFlowServerApplication();
|
||||
setField(app, "activeProfile", "prod");
|
||||
setField(app, "httpHost", "0.0.0.0");
|
||||
setField(app, "httpPort", 8085);
|
||||
// UNIONFLOW_DOMAIN non défini → System.getenv retourne null → branche domain==null
|
||||
// (Si la variable est définie dans l'env, le test sera skippé par la vraie valeur)
|
||||
String result = callBuildBaseUrl(app);
|
||||
assertThat(result).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,28 @@
|
||||
package dev.lions.unionflow.server;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
import io.quarkus.runtime.QuarkusApplication;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Tests pour UnionFlowServerApplication.
|
||||
*
|
||||
* <p>Couvre les méthodes privées (via réflexion) et les propriétés injectées.
|
||||
* La méthode run() n'est pas testée directement car elle appelle Quarkus.waitForExit()
|
||||
* (bloquant). Les méthodes logStartupBanner, logConfiguration, logEndpoints,
|
||||
* logArchitecture et buildBaseUrl sont testées via réflexion.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
*/
|
||||
@QuarkusTest
|
||||
@DisplayName("UnionFlowServerApplication")
|
||||
class UnionFlowServerApplicationTest {
|
||||
@@ -15,6 +30,16 @@ class UnionFlowServerApplicationTest {
|
||||
@Inject
|
||||
UnionFlowServerApplication application;
|
||||
|
||||
@ConfigProperty(name = "quarkus.application.name", defaultValue = "unionflow-server")
|
||||
String applicationName;
|
||||
|
||||
@ConfigProperty(name = "quarkus.application.version", defaultValue = "1.0.0")
|
||||
String applicationVersion;
|
||||
|
||||
// =========================================================================
|
||||
// Injection & Identity
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("Application est injectée et non null")
|
||||
void applicationInjected() {
|
||||
@@ -33,6 +58,10 @@ class UnionFlowServerApplicationTest {
|
||||
assertThat(application).isInstanceOf(UnionFlowServerApplication.class);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Réflexion — présence des méthodes
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("main method exists and is callable")
|
||||
void mainMethodExists() throws NoSuchMethodException {
|
||||
@@ -44,4 +73,184 @@ class UnionFlowServerApplicationTest {
|
||||
void runMethodExists() throws NoSuchMethodException {
|
||||
assertThat(UnionFlowServerApplication.class.getMethod("run", String[].class)).isNotNull();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Méthodes privées via réflexion
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("logStartupBanner - s'exécute sans exception")
|
||||
void logStartupBanner_sansException() throws Exception {
|
||||
Method method = UnionFlowServerApplication.class
|
||||
.getDeclaredMethod("logStartupBanner");
|
||||
method.setAccessible(true);
|
||||
|
||||
assertThatCode(() -> method.invoke(application))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("logConfiguration - s'exécute sans exception")
|
||||
void logConfiguration_sansException() throws Exception {
|
||||
Method method = UnionFlowServerApplication.class
|
||||
.getDeclaredMethod("logConfiguration");
|
||||
method.setAccessible(true);
|
||||
|
||||
assertThatCode(() -> method.invoke(application))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("logEndpoints - s'exécute sans exception (profil test)")
|
||||
void logEndpoints_sansException() throws Exception {
|
||||
Method method = UnionFlowServerApplication.class
|
||||
.getDeclaredMethod("logEndpoints");
|
||||
method.setAccessible(true);
|
||||
|
||||
assertThatCode(() -> method.invoke(application))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("logArchitecture - s'exécute sans exception")
|
||||
void logArchitecture_sansException() throws Exception {
|
||||
Method method = UnionFlowServerApplication.class
|
||||
.getDeclaredMethod("logArchitecture");
|
||||
method.setAccessible(true);
|
||||
|
||||
assertThatCode(() -> method.invoke(application))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// buildBaseUrl — profils dev/test et prod
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl - retourne une URL HTTP non-vide")
|
||||
void buildBaseUrl_retourneUrlHttp() throws Exception {
|
||||
Method method = UnionFlowServerApplication.class
|
||||
.getDeclaredMethod("buildBaseUrl");
|
||||
method.setAccessible(true);
|
||||
|
||||
String baseUrl = (String) method.invoke(application);
|
||||
|
||||
assertThat(baseUrl).isNotNull();
|
||||
assertThat(baseUrl).isNotEmpty();
|
||||
assertThat(baseUrl).startsWith("http://");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl - retourne une URL non-vide avec port numerique")
|
||||
void buildBaseUrl_retourneUrlAvecPort() throws Exception {
|
||||
Method method = UnionFlowServerApplication.class
|
||||
.getDeclaredMethod("buildBaseUrl");
|
||||
method.setAccessible(true);
|
||||
|
||||
String baseUrl = (String) method.invoke(application);
|
||||
|
||||
assertThat(baseUrl).isNotEmpty();
|
||||
// doit contenir un caractère ':' suivi d'un ou plusieurs chiffres
|
||||
assertThat(baseUrl).containsPattern(":\\d+");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl - profil prod avec UNIONFLOW_DOMAIN défini retourne URL https ou URL locale")
|
||||
void buildBaseUrl_prodProfileAvecDomain_retourneUrlHttps() throws Exception {
|
||||
// Note: Le CDI proxy ne propage pas les modifications de champs au bean sous-jacent.
|
||||
// Ce test vérifie que buildBaseUrl() retourne une URL valide (non vide) dans tous les cas.
|
||||
// Les branches spécifiques (prod+domain, httpHost personnalisé) sont couvertes par
|
||||
// UnionFlowServerApplicationBranchTest et UnionFlowServerApplicationBuildBaseUrlTest
|
||||
// qui instancient directement l'application sans CDI.
|
||||
Field profileField = UnionFlowServerApplication.class.getDeclaredField("activeProfile");
|
||||
profileField.setAccessible(true);
|
||||
String originalProfile = (String) profileField.get(application);
|
||||
profileField.set(application, "prod");
|
||||
try {
|
||||
Method method = UnionFlowServerApplication.class.getDeclaredMethod("buildBaseUrl");
|
||||
method.setAccessible(true);
|
||||
String baseUrl = (String) method.invoke(application);
|
||||
// L'URL peut être https:// (si UNIONFLOW_DOMAIN est défini et activeProfile=prod via bean)
|
||||
// ou http:// (si le proxy n'a pas propagé la valeur au bean sous-jacent)
|
||||
assertThat(baseUrl).isNotNull().isNotEmpty();
|
||||
assertThat(baseUrl).satisfiesAnyOf(
|
||||
url -> assertThat(url).startsWith("https://"),
|
||||
url -> assertThat(url).startsWith("http://")
|
||||
);
|
||||
} finally {
|
||||
profileField.set(application, originalProfile);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl - profil prod sans UNIONFLOW_DOMAIN retourne URL http")
|
||||
void buildBaseUrl_prodProfileSansDomain_retourneUrlHttp() throws Exception {
|
||||
Field profileField = UnionFlowServerApplication.class.getDeclaredField("activeProfile");
|
||||
profileField.setAccessible(true);
|
||||
String originalProfile = (String) profileField.get(application);
|
||||
profileField.set(application, "prod");
|
||||
try {
|
||||
Method method = UnionFlowServerApplication.class.getDeclaredMethod("buildBaseUrl");
|
||||
method.setAccessible(true);
|
||||
String baseUrl = (String) method.invoke(application);
|
||||
// UNIONFLOW_DOMAIN n'est pas définie en test → branche else → http://localhost:port
|
||||
assertThat(baseUrl).isNotNull().isNotEmpty();
|
||||
} finally {
|
||||
profileField.set(application, originalProfile);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildBaseUrl - httpHost personnalisé utilisé directement ou localhost (CDI proxy)")
|
||||
void buildBaseUrl_httpHostPersonnalise_utiliseDansUrl() throws Exception {
|
||||
// Note: Le CDI proxy ne propage pas les modifications de champs au bean sous-jacent.
|
||||
// La branche httpHost != "0.0.0.0" est couverte par UnionFlowServerApplicationBranchTest.
|
||||
Field hostField = UnionFlowServerApplication.class.getDeclaredField("httpHost");
|
||||
hostField.setAccessible(true);
|
||||
String originalHost = (String) hostField.get(application);
|
||||
hostField.set(application, "192.168.1.100");
|
||||
try {
|
||||
Method method = UnionFlowServerApplication.class.getDeclaredMethod("buildBaseUrl");
|
||||
method.setAccessible(true);
|
||||
String baseUrl = (String) method.invoke(application);
|
||||
// L'URL peut contenir "192.168.1.100" (si le proxy a propagé au bean)
|
||||
// ou "localhost" (si le proxy ne propage pas la valeur)
|
||||
assertThat(baseUrl).isNotNull().isNotEmpty().startsWith("http");
|
||||
} finally {
|
||||
hostField.set(application, originalHost);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("logEndpoints - profil dev affiche Dev UI et H2 Console sans exception")
|
||||
void logEndpoints_devProfile_sansException() throws Exception {
|
||||
Field profileField = UnionFlowServerApplication.class.getDeclaredField("activeProfile");
|
||||
profileField.setAccessible(true);
|
||||
String originalProfile = (String) profileField.get(application);
|
||||
profileField.set(application, "dev");
|
||||
try {
|
||||
Method method = UnionFlowServerApplication.class.getDeclaredMethod("logEndpoints");
|
||||
method.setAccessible(true);
|
||||
assertThatCode(() -> method.invoke(application)).doesNotThrowAnyException();
|
||||
} finally {
|
||||
profileField.set(application, originalProfile);
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Config properties injectées
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("Application name est configurée")
|
||||
void applicationNameConfigured() {
|
||||
assertThat(applicationName).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Application version est configurée")
|
||||
void applicationVersionConfigured() {
|
||||
assertThat(applicationVersion).isNotBlank();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package dev.lions.unionflow.server.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import jakarta.ws.rs.client.ClientRequestContext;
|
||||
import jakarta.ws.rs.core.MultivaluedHashMap;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URI;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test SANS @QuarkusTest pour couvrir la branche {@code securityIdentity == null}
|
||||
* dans {@link JwtPropagationFilter#filter} (L29).
|
||||
*
|
||||
* <p>En contexte CDI, {@code securityIdentity} est toujours un proxy non-null.
|
||||
* Cette branche n'est atteignable qu'en instanciant {@link JwtPropagationFilter} directement
|
||||
* et en laissant le champ à {@code null} (valeur par défaut Java).
|
||||
*/
|
||||
class JwtPropagationFilterNullIdentityTest {
|
||||
|
||||
private ClientRequestContext buildMockContext() {
|
||||
MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>();
|
||||
ClientRequestContext ctx = mock(ClientRequestContext.class);
|
||||
when(ctx.getHeaders()).thenReturn(headers);
|
||||
when(ctx.getUri()).thenReturn(URI.create("http://localhost/api/test"));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("filter : securityIdentity null → warn 'Pas de SecurityIdentity', pas de header Authorization (branche null L29)")
|
||||
void filter_securityIdentityNull_doesNotPropagate() throws Exception {
|
||||
// Instanciation directe — securityIdentity reste null (champ non injecté)
|
||||
JwtPropagationFilter filter = new JwtPropagationFilter();
|
||||
// securityIdentity est null par défaut (pas d'injection CDI)
|
||||
|
||||
// Vérifie que le champ est bien null
|
||||
Field siField = JwtPropagationFilter.class.getDeclaredField("securityIdentity");
|
||||
siField.setAccessible(true);
|
||||
assertThat(siField.get(filter)).isNull();
|
||||
|
||||
ClientRequestContext ctx = buildMockContext();
|
||||
filter.filter(ctx); // ne doit pas lever d'exception
|
||||
|
||||
// Pas de header Authorization ajouté
|
||||
assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package dev.lions.unionflow.server.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import io.quarkus.test.InjectMock;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.client.ClientRequestContext;
|
||||
import jakarta.ws.rs.core.MultivaluedHashMap;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* Tests pour {@link JwtPropagationFilter}.
|
||||
*
|
||||
* <p>Couvre toutes les branches de {@code filter()} :
|
||||
* <ul>
|
||||
* <li>securityIdentity anonyme → pas de propagation</li>
|
||||
* <li>OidcJwtCallerPrincipal avec token valide → header Authorization propagé</li>
|
||||
* <li>OidcJwtCallerPrincipal avec token vide/blank → pas de propagation</li>
|
||||
* <li>JsonWebToken (non OidcJwtCallerPrincipal) avec token valide → header propagé</li>
|
||||
* <li>Principal générique (ni OidcJwtCallerPrincipal ni JsonWebToken) → log warn, pas de header</li>
|
||||
* </ul>
|
||||
*/
|
||||
@QuarkusTest
|
||||
class JwtPropagationFilterTest {
|
||||
|
||||
@Inject
|
||||
JwtPropagationFilter filter;
|
||||
|
||||
@InjectMock
|
||||
SecurityIdentity securityIdentity;
|
||||
|
||||
private ClientRequestContext buildMockContext() {
|
||||
MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>();
|
||||
ClientRequestContext ctx = mock(ClientRequestContext.class);
|
||||
when(ctx.getHeaders()).thenReturn(headers);
|
||||
when(ctx.getUri()).thenReturn(URI.create("http://localhost/api/test"));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// ─── Branch: securityIdentity.isAnonymous() = true → skip ────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("filter : identité anonyme → pas de header Authorization ajouté")
|
||||
void filter_anonymousIdentity_doesNotPropagateToken() throws IOException {
|
||||
when(securityIdentity.isAnonymous()).thenReturn(true);
|
||||
|
||||
ClientRequestContext ctx = buildMockContext();
|
||||
filter.filter(ctx);
|
||||
|
||||
assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
|
||||
}
|
||||
|
||||
// ─── Branch: OidcJwtCallerPrincipal avec token valide ────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("filter : OidcJwtCallerPrincipal avec token valide → Authorization propagé")
|
||||
void filter_oidcPrincipalWithValidToken_propagatesToken() throws IOException {
|
||||
OidcJwtCallerPrincipal principal = mock(OidcJwtCallerPrincipal.class);
|
||||
when(principal.getRawToken()).thenReturn("valid-jwt-token");
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(principal);
|
||||
|
||||
ClientRequestContext ctx = buildMockContext();
|
||||
filter.filter(ctx);
|
||||
|
||||
Object authHeader = ctx.getHeaders().getFirst("Authorization");
|
||||
assertThat(authHeader).isNotNull();
|
||||
assertThat(authHeader.toString()).isEqualTo("Bearer valid-jwt-token");
|
||||
}
|
||||
|
||||
// ─── Branch: OidcJwtCallerPrincipal avec token blank ─────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("filter : OidcJwtCallerPrincipal avec token blank → pas de propagation")
|
||||
void filter_oidcPrincipalWithBlankToken_doesNotPropagate() throws IOException {
|
||||
OidcJwtCallerPrincipal principal = mock(OidcJwtCallerPrincipal.class);
|
||||
when(principal.getRawToken()).thenReturn(" ");
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(principal);
|
||||
|
||||
ClientRequestContext ctx = buildMockContext();
|
||||
filter.filter(ctx);
|
||||
|
||||
assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
|
||||
}
|
||||
|
||||
// ─── Branch: JsonWebToken (NOT OidcJwtCallerPrincipal) ───────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("filter : JsonWebToken principal (non-OIDC) avec token valide → Authorization propagé")
|
||||
void filter_jsonWebTokenPrincipalWithValidToken_propagatesToken() throws IOException {
|
||||
// JsonWebToken mock n'est PAS OidcJwtCallerPrincipal → branche else-if
|
||||
JsonWebToken jwt = mock(JsonWebToken.class);
|
||||
when(jwt.getRawToken()).thenReturn("valid-jwt-from-JsonWebToken");
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(jwt);
|
||||
|
||||
ClientRequestContext ctx = buildMockContext();
|
||||
filter.filter(ctx);
|
||||
|
||||
Object authHeader = ctx.getHeaders().getFirst("Authorization");
|
||||
assertThat(authHeader).isNotNull();
|
||||
assertThat(authHeader.toString()).isEqualTo("Bearer valid-jwt-from-JsonWebToken");
|
||||
}
|
||||
|
||||
// ─── Branch: principal ni OidcJwtCallerPrincipal ni JsonWebToken ─────────
|
||||
|
||||
@Test
|
||||
@DisplayName("filter : principal inconnu (ni OIDC ni JWT) → log warn, pas de header Authorization")
|
||||
void filter_unknownPrincipalType_doesNotPropagate() throws IOException {
|
||||
// Principal générique — ni OidcJwtCallerPrincipal ni JsonWebToken → branche else
|
||||
Principal genericPrincipal = mock(Principal.class);
|
||||
when(genericPrincipal.getName()).thenReturn("some-user");
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(genericPrincipal);
|
||||
|
||||
ClientRequestContext ctx = buildMockContext();
|
||||
filter.filter(ctx);
|
||||
|
||||
assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
|
||||
}
|
||||
|
||||
// ─── Branch: OidcJwtCallerPrincipal avec token null → pas de propagation ─
|
||||
|
||||
@Test
|
||||
@DisplayName("filter : OidcJwtCallerPrincipal avec token null → pas de propagation (branche token==null L35)")
|
||||
void filter_oidcPrincipalWithNullToken_doesNotPropagate() throws IOException {
|
||||
OidcJwtCallerPrincipal principal = mock(OidcJwtCallerPrincipal.class);
|
||||
when(principal.getRawToken()).thenReturn(null); // null → condition false
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(principal);
|
||||
|
||||
ClientRequestContext ctx = buildMockContext();
|
||||
filter.filter(ctx);
|
||||
|
||||
assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("filter : JsonWebToken principal avec token null → pas de propagation")
|
||||
void filter_jsonWebTokenWithNullToken_doesNotPropagate() throws IOException {
|
||||
JsonWebToken jwt = mock(JsonWebToken.class);
|
||||
when(jwt.getRawToken()).thenReturn(null);
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(jwt);
|
||||
|
||||
ClientRequestContext ctx = buildMockContext();
|
||||
filter.filter(ctx);
|
||||
|
||||
assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("filter : JsonWebToken principal avec token blank → pas de propagation")
|
||||
void filter_jsonWebTokenWithBlankToken_doesNotPropagate() throws IOException {
|
||||
JsonWebToken jwt = mock(JsonWebToken.class);
|
||||
when(jwt.getRawToken()).thenReturn(" ");
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(jwt);
|
||||
|
||||
ClientRequestContext ctx = buildMockContext();
|
||||
filter.filter(ctx);
|
||||
|
||||
assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package dev.lions.unionflow.server.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import io.quarkus.test.InjectMock;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.enterprise.inject.Instance;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.core.MultivaluedHashMap;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests pour les branches de {@link OidcTokenPropagationHeadersFactory#update}.
|
||||
*/
|
||||
@QuarkusTest
|
||||
@DisplayName("OidcTokenPropagationHeadersFactory — branches restantes (5I, 5B)")
|
||||
class OidcTokenPropagationHeadersFactoryBranchesTest {
|
||||
|
||||
@Inject
|
||||
OidcTokenPropagationHeadersFactory factory;
|
||||
|
||||
@InjectMock
|
||||
SecurityIdentity securityIdentity;
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec identity null → warn + résultat vide")
|
||||
void update_identityNull_avertissementEtResultatVide() {
|
||||
// when(securityIdentity.isAnonymous()) n'est pas appelé car identity est null
|
||||
// mais l'implémentation vérifie identity != null avant isAnonymous()
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(null);
|
||||
// L'identité retourne un principal null — ce n'est pas null mais le principal l'est
|
||||
// ce qui provoque une NPE dans "getPrincipal() instanceof OidcJwtCallerPrincipal"
|
||||
// → la branche est atteinte via securityIdentity.getPrincipal() = null
|
||||
// mais le code vérifie "identity != null && !identity.isAnonymous()" d'abord.
|
||||
// Si identity n'est pas null mais getPrincipal() retourne null :
|
||||
// → "identity.getPrincipal() instanceof OidcJwtCallerPrincipal" = false (null instanceof = false)
|
||||
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
// Pas d'Authorization header
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.containsKey("Authorization")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec identité anonyme → warn + résultat vide (branche isAnonymous)")
|
||||
void update_identiteAnonyme_avertissementEtResultatVide() {
|
||||
when(securityIdentity.isAnonymous()).thenReturn(true);
|
||||
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.containsKey("Authorization")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec incomingHeaders null → stratégie 1 sautée, stratégie 2 évaluée")
|
||||
void update_incomingHeadersNull_strategie1Sautee() {
|
||||
when(securityIdentity.isAnonymous()).thenReturn(true);
|
||||
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(null, outgoing);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.containsKey("Authorization")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec OidcJwtCallerPrincipal token null → warn + résultat vide")
|
||||
void update_oidcPrincipalTokenNull_avertissementEtResultatVide() {
|
||||
OidcJwtCallerPrincipal mockPrincipal = mock(OidcJwtCallerPrincipal.class);
|
||||
when(mockPrincipal.getRawToken()).thenReturn(null);
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(mockPrincipal);
|
||||
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.containsKey("Authorization")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec Authorization null dans incomingHeaders → passe à stratégie 2")
|
||||
void update_incomingAuthorizationNullValue_passeAStrategie2() {
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
incoming.add("Authorization", null);
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(true);
|
||||
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.containsKey("Authorization")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec principal non-OidcJwtCallerPrincipal → warn + résultat vide")
|
||||
void update_nonOidcPrincipal_avertissementEtResultatVide() {
|
||||
java.security.Principal nonOidcPrincipal = () -> "simple-principal";
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(nonOidcPrincipal);
|
||||
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.containsKey("Authorization")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec Authorization valide + autres headers → copie uniquement Authorization")
|
||||
void update_avecAutresHeaders_copieUniquementAuthorization() {
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
incoming.add("Authorization", "Bearer valid-token-abc");
|
||||
incoming.add("Content-Type", "application/json");
|
||||
incoming.add("Accept", "application/json");
|
||||
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result.getFirst("Authorization")).isEqualTo("Bearer valid-token-abc");
|
||||
assertThat(result.size()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec OidcJwtCallerPrincipal token non-blank → 'Bearer ' préfixé au token")
|
||||
void update_oidcPrincipalTokenValide_bearerPrefixe() {
|
||||
OidcJwtCallerPrincipal mockPrincipal = mock(OidcJwtCallerPrincipal.class);
|
||||
String rawToken = "eyJhbGciOiJSUzI1NiJ9.payload.signature";
|
||||
when(mockPrincipal.getRawToken()).thenReturn(rawToken);
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(mockPrincipal);
|
||||
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result.getFirst("Authorization")).isEqualTo("Bearer " + rawToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package dev.lions.unionflow.server.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import jakarta.enterprise.inject.Instance;
|
||||
import jakarta.ws.rs.core.MultivaluedHashMap;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import java.lang.reflect.Field;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test SANS @QuarkusTest pour couvrir la branche {@code identity == null}
|
||||
* dans {@link OidcTokenPropagationHeadersFactory#update} (L49).
|
||||
*
|
||||
* <p>En contexte CDI, {@code securityIdentity.get()} ne retourne jamais null.
|
||||
* On instancie {@link OidcTokenPropagationHeadersFactory} directement et injecte
|
||||
* un {@link Instance} dont {@code get()} retourne null.
|
||||
*/
|
||||
class OidcTokenPropagationHeadersFactoryNullIdentityTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
@DisplayName("update : securityIdentity.get() retourne null → warn + résultat vide (branche identity==null L49)")
|
||||
void update_identityGetReturnsNull_returnsEmptyResult() throws Exception {
|
||||
OidcTokenPropagationHeadersFactory factory = new OidcTokenPropagationHeadersFactory();
|
||||
|
||||
// Crée un mock Instance<SecurityIdentity> dont get() retourne null
|
||||
Instance<SecurityIdentity> mockInstance = mock(Instance.class);
|
||||
when(mockInstance.get()).thenReturn(null);
|
||||
|
||||
Field siField = OidcTokenPropagationHeadersFactory.class.getDeclaredField("securityIdentity");
|
||||
siField.setAccessible(true);
|
||||
siField.set(factory, mockInstance);
|
||||
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
// Pas d'Authorization → stratégie 1 sautée → stratégie 2 : identity == null → warn
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.containsKey("Authorization")).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package dev.lions.unionflow.server.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import io.quarkus.test.InjectMock;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.core.MultivaluedHashMap;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests pour {@link OidcTokenPropagationHeadersFactory#update} — branche OidcJwtCallerPrincipal
|
||||
* (lignes 50-60).
|
||||
*
|
||||
* <p>Les tests dans {@link OidcTokenPropagationHeadersFactoryTest} utilisent {@code @TestSecurity}
|
||||
* dont le principal N'EST PAS un {@link OidcJwtCallerPrincipal} → lignes 50-60 jamais atteintes.
|
||||
* Ce test mock {@code SecurityIdentity} pour retourner un mock de {@link OidcJwtCallerPrincipal},
|
||||
* couvrant les 2 sous-branches (token valide et token blank).
|
||||
*/
|
||||
@QuarkusTest
|
||||
@DisplayName("OidcTokenPropagationHeadersFactory — branche OidcJwtCallerPrincipal (lignes 50-60)")
|
||||
class OidcTokenPropagationHeadersFactoryOidcTest {
|
||||
|
||||
@Inject
|
||||
OidcTokenPropagationHeadersFactory factory;
|
||||
|
||||
@InjectMock
|
||||
SecurityIdentity securityIdentity;
|
||||
|
||||
// =========================================================================
|
||||
// Cas 1 : OidcJwtCallerPrincipal avec token valide → propagation (lignes 50-57)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec OidcJwtCallerPrincipal et token valide — couvre lignes 50-57")
|
||||
void update_withOidcPrincipalAndValidToken_propagatesToken() {
|
||||
OidcJwtCallerPrincipal mockPrincipal = mock(OidcJwtCallerPrincipal.class);
|
||||
when(mockPrincipal.getRawToken()).thenReturn("eyJhbGciOiJSUzI1NiJ9.valid-token");
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(mockPrincipal);
|
||||
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result.getFirst("Authorization"))
|
||||
.isEqualTo("Bearer eyJhbGciOiJSUzI1NiJ9.valid-token");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Cas 2 : OidcJwtCallerPrincipal avec token blank → warn (ligne 59)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec OidcJwtCallerPrincipal et token blank — couvre ligne 59 (warn + pas de propagation)")
|
||||
void update_withOidcPrincipalAndBlankToken_noTokenPropagated() {
|
||||
OidcJwtCallerPrincipal mockPrincipal = mock(OidcJwtCallerPrincipal.class);
|
||||
when(mockPrincipal.getRawToken()).thenReturn(" "); // blank token → branche else ligne 58-60
|
||||
|
||||
when(securityIdentity.isAnonymous()).thenReturn(false);
|
||||
when(securityIdentity.getPrincipal()).thenReturn(mockPrincipal);
|
||||
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result.containsKey("Authorization")).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package dev.lions.unionflow.server.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.security.TestSecurity;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.core.MultivaluedHashMap;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests pour {@link OidcTokenPropagationHeadersFactory}.
|
||||
*
|
||||
* <p>Couvre :
|
||||
* <ul>
|
||||
* <li>Stratégie 1 : propagation depuis incomingHeaders (Authorization présent)</li>
|
||||
* <li>Stratégie 2a : identité anonyme → aucun token propagé</li>
|
||||
* <li>Stratégie 2b : identité authentifiée mais principal non-OidcJwtCallerPrincipal (TestSecurity)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@QuarkusTest
|
||||
class OidcTokenPropagationHeadersFactoryTest {
|
||||
|
||||
@Inject
|
||||
OidcTokenPropagationHeadersFactory factory;
|
||||
|
||||
// ─── Stratégie 1 : Authorization présent dans incomingHeaders ───────────
|
||||
|
||||
@Test
|
||||
@DisplayName("update copie Authorization depuis incomingHeaders si présent")
|
||||
void update_withIncomingAuthHeader_propagatesToken() {
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
incoming.add("Authorization", "Bearer test-token-xyz");
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
assertThat(result.getFirst("Authorization")).isEqualTo("Bearer test-token-xyz");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update avec Authorization vide dans incomingHeaders passe à la stratégie 2")
|
||||
void update_withBlankIncomingAuthHeader_fallsToStrategy2() {
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
incoming.add("Authorization", " ");
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
// Stratégie 1 échoue (vide) → tombe sur stratégie 2 (SecurityIdentity)
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
// Pas de token propagé depuis les incomingHeaders (blank)
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update sans Authorization dans incomingHeaders passe à la stratégie 2")
|
||||
void update_withoutIncomingAuthHeader_usesStrategy2() {
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
// Sans identité OIDC → résultat vide (pas de token propagé)
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
// ─── Stratégie 2b : identité authentifiée TestSecurity (pas OidcJwtCallerPrincipal) ─
|
||||
|
||||
@Test
|
||||
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
|
||||
@DisplayName("update avec identité TestSecurity (non-OIDC) retourne map vide (principal non-OIDC)")
|
||||
void update_withTestSecurityIdentity_returnsEmptyMap() {
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
// Avec TestSecurity, SecurityIdentity est non-anonyme mais le principal
|
||||
// n'est pas un OidcJwtCallerPrincipal → branche "Principal n'est pas OidcJwtCallerPrincipal"
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
// Aucun token propagé (pas d'OidcJwtCallerPrincipal)
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.containsKey("Authorization")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
|
||||
@DisplayName("update avec incomingHeaders Authorization valide retourne le token même avec @TestSecurity")
|
||||
void update_withValidIncomingAndTestSecurity_propagatesIncomingToken() {
|
||||
MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
|
||||
incoming.add("Authorization", "Bearer incoming-token");
|
||||
MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();
|
||||
|
||||
MultivaluedMap<String, String> result = factory.update(incoming, outgoing);
|
||||
|
||||
// Stratégie 1 réussit → retourne le token incoming
|
||||
assertThat(result.getFirst("Authorization")).isEqualTo("Bearer incoming-token");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package dev.lions.unionflow.server.dto;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("EvenementMobileDTO")
|
||||
class EvenementMobileDTOTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("fromEntity: retourne null si evenement null")
|
||||
void fromEntity_null_returnsNull() {
|
||||
assertThat(EvenementMobileDTO.fromEntity(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fromEntity: convertit correctement une entité Evenement")
|
||||
void fromEntity_validEntity_mapsAllFields() {
|
||||
Evenement e = new Evenement();
|
||||
e.setId(UUID.randomUUID());
|
||||
e.setTitre("AG 2025");
|
||||
e.setDescription("Assemblée générale");
|
||||
e.setDateDebut(LocalDateTime.of(2025, 6, 1, 10, 0));
|
||||
e.setDateFin(LocalDateTime.of(2025, 6, 1, 12, 0));
|
||||
e.setLieu("Salle A");
|
||||
e.setTypeEvenement("ASSEMBLEE_GENERALE");
|
||||
e.setStatut("PLANIFIE");
|
||||
e.setCapaciteMax(100);
|
||||
e.setPrix(new BigDecimal("5000.00"));
|
||||
e.setVisiblePublic(true);
|
||||
e.setInscriptionRequise(true);
|
||||
e.setActif(true);
|
||||
|
||||
Membre organisateur = new Membre();
|
||||
organisateur.setId(UUID.randomUUID());
|
||||
organisateur.setNom("Diallo");
|
||||
organisateur.setPrenom("Amadou");
|
||||
e.setOrganisateur(organisateur);
|
||||
|
||||
Organisation org = new Organisation();
|
||||
org.setId(UUID.randomUUID());
|
||||
org.setNom("Lions Club");
|
||||
e.setOrganisation(org);
|
||||
|
||||
EvenementMobileDTO dto = EvenementMobileDTO.fromEntity(e);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getTitre()).isEqualTo("AG 2025");
|
||||
assertThat(dto.getStatut()).isEqualTo("PLANIFIE");
|
||||
assertThat(dto.getMaxParticipants()).isEqualTo(100);
|
||||
assertThat(dto.getOrganisateurId()).isEqualTo(organisateur.getId());
|
||||
assertThat(dto.getOrganisationId()).isEqualTo(org.getId());
|
||||
assertThat(dto.getOrganisationNom()).isEqualTo("Lions Club");
|
||||
assertThat(dto.getEstPublic()).isTrue();
|
||||
assertThat(dto.getCout()).isEqualByComparingTo("5000.00");
|
||||
assertThat(dto.getDevise()).isEqualTo("XOF");
|
||||
assertThat(dto.getPriorite()).isEqualTo("MOYENNE");
|
||||
assertThat(dto.getTags()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fromEntity: null organisateur et organisation → IDs null")
|
||||
void fromEntity_nullRelations_idsNull() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now());
|
||||
e.setStatut("PLANIFIE");
|
||||
e.setOrganisateur(null);
|
||||
e.setOrganisation(null);
|
||||
|
||||
EvenementMobileDTO dto = EvenementMobileDTO.fromEntity(e);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getOrganisateurId()).isNull();
|
||||
assertThat(dto.getOrganisateurNom()).isNull();
|
||||
assertThat(dto.getOrganisationId()).isNull();
|
||||
assertThat(dto.getOrganisationNom()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fromEntity: typeEvenement null → type null")
|
||||
void fromEntity_nullTypeEvenement_typeNull() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now());
|
||||
e.setTypeEvenement(null);
|
||||
|
||||
EvenementMobileDTO dto = EvenementMobileDTO.fromEntity(e);
|
||||
|
||||
assertThat(dto.getType()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fromEntity: statut null → statut PLANIFIE")
|
||||
void fromEntity_nullStatut_defaultsPlanifie() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now());
|
||||
e.setStatut(null);
|
||||
|
||||
EvenementMobileDTO dto = EvenementMobileDTO.fromEntity(e);
|
||||
|
||||
assertThat(dto.getStatut()).isEqualTo("PLANIFIE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getters/setters")
|
||||
void gettersSetters() {
|
||||
EvenementMobileDTO dto = EvenementMobileDTO.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.titre("Test")
|
||||
.statut("CONFIRME")
|
||||
.build();
|
||||
assertThat(dto.getTitre()).isEqualTo("Test");
|
||||
assertThat(dto.getStatut()).isEqualTo("CONFIRME");
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,94 @@ class AdresseTest {
|
||||
assertThat(a.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
// ── Branch coverage: getAdresseComplete ────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("getAdresseComplete: only complementAdresse (sb empty at append)")
|
||||
void getAdresseComplete_onlyComplementAdresse() {
|
||||
Adresse a = new Adresse();
|
||||
a.setTypeAdresse("SIEGE");
|
||||
a.setComplementAdresse("Bât B");
|
||||
assertThat(a.getAdresseComplete()).isEqualTo("Bât B");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAdresseComplete: only codePostal (sb empty at append)")
|
||||
void getAdresseComplete_onlyCodePostal() {
|
||||
Adresse a = new Adresse();
|
||||
a.setTypeAdresse("SIEGE");
|
||||
a.setCodePostal("75002");
|
||||
assertThat(a.getAdresseComplete()).isEqualTo("75002");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAdresseComplete: only region (sb empty at append)")
|
||||
void getAdresseComplete_onlyRegion() {
|
||||
Adresse a = new Adresse();
|
||||
a.setTypeAdresse("SIEGE");
|
||||
a.setRegion("Bretagne");
|
||||
assertThat(a.getAdresseComplete()).isEqualTo("Bretagne");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAdresseComplete: only pays (sb empty at append)")
|
||||
void getAdresseComplete_onlyPays() {
|
||||
Adresse a = new Adresse();
|
||||
a.setTypeAdresse("SIEGE");
|
||||
a.setPays("France");
|
||||
assertThat(a.getAdresseComplete()).isEqualTo("France");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAdresseComplete: adresse + codePostal (skip complementAdresse), triggers sb>0 for codePostal")
|
||||
void getAdresseComplete_adresseAndCodePostal() {
|
||||
Adresse a = new Adresse();
|
||||
a.setTypeAdresse("SIEGE");
|
||||
a.setAdresse("2 rue X");
|
||||
a.setCodePostal("13000");
|
||||
assertThat(a.getAdresseComplete()).isEqualTo("2 rue X, 13000");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAdresseComplete: codePostal + ville (sb>0 for ville space separator)")
|
||||
void getAdresseComplete_codePostalAndVille() {
|
||||
Adresse a = new Adresse();
|
||||
a.setTypeAdresse("SIEGE");
|
||||
a.setCodePostal("69001");
|
||||
a.setVille("Lyon");
|
||||
assertThat(a.getAdresseComplete()).isEqualTo("69001 Lyon");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAdresseComplete: adresse with empty string fields ignored")
|
||||
void getAdresseComplete_emptyStringFieldsIgnored() {
|
||||
Adresse a = new Adresse();
|
||||
a.setTypeAdresse("SIEGE");
|
||||
a.setAdresse("3 rue Y");
|
||||
a.setComplementAdresse("");
|
||||
a.setCodePostal("");
|
||||
a.setVille("");
|
||||
a.setRegion("");
|
||||
a.setPays("");
|
||||
assertThat(a.getAdresseComplete()).isEqualTo("3 rue Y");
|
||||
}
|
||||
|
||||
// ── Branch coverage manquante ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* L107 branch manquante : adresse != null mais adresse.isEmpty() → false (deuxième branche du &&)
|
||||
* → `if (adresse != null && !adresse.isEmpty())` → false (adresse est vide "")
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("getAdresseComplete: adresse = empty string (non null) → ignorée (branche isEmpty)")
|
||||
void getAdresseComplete_adresseEmptyString_ignored() {
|
||||
Adresse a = new Adresse();
|
||||
a.setTypeAdresse("SIEGE");
|
||||
a.setAdresse(""); // non null mais vide → condition !adresse.isEmpty() est false → ignorée
|
||||
a.setVille("Dakar");
|
||||
assertThat(a.getAdresseComplete()).isEqualTo("Dakar");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("relations: organisation, membre, evenement")
|
||||
void relations() {
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("ApproverAction")
|
||||
class ApproverActionTest {
|
||||
|
||||
private static TransactionApproval newApproval() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setId(UUID.randomUUID());
|
||||
return ta;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// approve
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("approve: positionne la décision à APPROVED")
|
||||
void approve_setsDecisionApproved() {
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.setDecision("PENDING");
|
||||
a.approve("Tout est correct");
|
||||
assertThat(a.getDecision()).isEqualTo("APPROVED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("approve: positionne le commentaire")
|
||||
void approve_setsComment() {
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.approve("Approuvé sans réserve");
|
||||
assertThat(a.getComment()).isEqualTo("Approuvé sans réserve");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("approve: positionne decidedAt à une date non nulle")
|
||||
void approve_setsDecidedAt() {
|
||||
LocalDateTime before = LocalDateTime.now().minusSeconds(1);
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.approve("OK");
|
||||
assertThat(a.getDecidedAt()).isNotNull().isAfterOrEqualTo(before);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// reject
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("reject: positionne la décision à REJECTED")
|
||||
void reject_setsDecisionRejected() {
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.setDecision("PENDING");
|
||||
a.reject("Montant trop élevé");
|
||||
assertThat(a.getDecision()).isEqualTo("REJECTED");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("reject: positionne la raison dans le commentaire")
|
||||
void reject_setsReason() {
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.reject("Justificatif manquant");
|
||||
assertThat(a.getComment()).isEqualTo("Justificatif manquant");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("reject: positionne decidedAt à une date non nulle")
|
||||
void reject_setsDecidedAt() {
|
||||
LocalDateTime before = LocalDateTime.now().minusSeconds(1);
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.reject("Raison");
|
||||
assertThat(a.getDecidedAt()).isNotNull().isAfterOrEqualTo(before);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// onCreate (réflexion)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise decision à PENDING si null")
|
||||
void onCreate_initializesDecisionIfNull() throws Exception {
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.setDecision(null);
|
||||
|
||||
Method onCreate = ApproverAction.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(a);
|
||||
|
||||
assertThat(a.getDecision()).isEqualTo("PENDING");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: ne remplace pas decision si déjà renseigné")
|
||||
void onCreate_doesNotOverrideDecision() throws Exception {
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.setDecision("APPROVED");
|
||||
|
||||
Method onCreate = ApproverAction.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(a);
|
||||
|
||||
assertThat(a.getDecision()).isEqualTo("APPROVED");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// builder
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("builder: positionne tous les champs correctement")
|
||||
void builder_setsAllFields() {
|
||||
TransactionApproval approval = newApproval();
|
||||
UUID approverId = UUID.randomUUID();
|
||||
LocalDateTime decidedAt = LocalDateTime.of(2026, 3, 20, 14, 30);
|
||||
|
||||
ApproverAction a = ApproverAction.builder()
|
||||
.approval(approval)
|
||||
.approverId(approverId)
|
||||
.approverName("Mamadou Diallo")
|
||||
.approverRole("TRESORIER")
|
||||
.decision("APPROVED")
|
||||
.comment("Dépense justifiée")
|
||||
.decidedAt(decidedAt)
|
||||
.build();
|
||||
|
||||
assertThat(a.getApproval()).isSameAs(approval);
|
||||
assertThat(a.getApproverId()).isEqualTo(approverId);
|
||||
assertThat(a.getApproverName()).isEqualTo("Mamadou Diallo");
|
||||
assertThat(a.getApproverRole()).isEqualTo("TRESORIER");
|
||||
assertThat(a.getDecision()).isEqualTo("APPROVED");
|
||||
assertThat(a.getComment()).isEqualTo("Dépense justifiée");
|
||||
assertThat(a.getDecidedAt()).isEqualTo(decidedAt);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getters / setters
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("getters/setters: tous les champs accessibles en lecture/écriture")
|
||||
void gettersSetters_workCorrectly() {
|
||||
TransactionApproval approval = newApproval();
|
||||
UUID approverId = UUID.randomUUID();
|
||||
LocalDateTime decidedAt = LocalDateTime.now();
|
||||
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.setApproval(approval);
|
||||
a.setApproverId(approverId);
|
||||
a.setApproverName("Aïssata Koné");
|
||||
a.setApproverRole("VICE_PRESIDENT");
|
||||
a.setDecision("REJECTED");
|
||||
a.setComment("Documents insuffisants");
|
||||
a.setDecidedAt(decidedAt);
|
||||
|
||||
assertThat(a.getApproval()).isSameAs(approval);
|
||||
assertThat(a.getApproverId()).isEqualTo(approverId);
|
||||
assertThat(a.getApproverName()).isEqualTo("Aïssata Koné");
|
||||
assertThat(a.getApproverRole()).isEqualTo("VICE_PRESIDENT");
|
||||
assertThat(a.getDecision()).isEqualTo("REJECTED");
|
||||
assertThat(a.getComment()).isEqualTo("Documents insuffisants");
|
||||
assertThat(a.getDecidedAt()).isEqualTo(decidedAt);
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,20 @@ class AyantDroitTest {
|
||||
assertThat(a.isCouvertAujourdhui()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isCouvertAujourdhui: false si actif=false même avec dates valides")
|
||||
void isCouvertAujourdhui_false_whenActifFalse() {
|
||||
AyantDroit a = new AyantDroit();
|
||||
a.setMembreOrganisation(newMembreOrganisation());
|
||||
a.setPrenom("X");
|
||||
a.setNom("Y");
|
||||
a.setLienParente(LienParente.ENFANT);
|
||||
a.setDateDebutCouverture(LocalDate.now().minusDays(1));
|
||||
a.setDateFinCouverture(null);
|
||||
a.setActif(false);
|
||||
assertThat(a.isCouvertAujourdhui()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isCouvertAujourdhui: true si actif et dates couvrent aujourd'hui")
|
||||
void isCouvertAujourdhui_true() {
|
||||
@@ -151,4 +165,62 @@ class AyantDroitTest {
|
||||
a.setLienParente(LienParente.ENFANT);
|
||||
assertThat(a.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
// ── Branch coverage manquantes ─────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("isCouvertAujourdhui: dateDebutCouverture null → condition L85 null short-circuit → continue (branche dateDebutCouverture==null)")
|
||||
void isCouvertAujourdhui_debutNull_branchNullShortCircuit() {
|
||||
AyantDroit a = new AyantDroit();
|
||||
a.setMembreOrganisation(newMembreOrganisation());
|
||||
a.setPrenom("X");
|
||||
a.setNom("Y");
|
||||
a.setLienParente(LienParente.ENFANT);
|
||||
a.setDateDebutCouverture(null); // null → dateDebutCouverture != null = false → skip at L85
|
||||
a.setDateFinCouverture(null); // null → dateFinCouverture != null = false → skip at L87
|
||||
a.setActif(true);
|
||||
// Pas de retour false aux conditions → return Boolean.TRUE.equals(true) = true
|
||||
assertThat(a.isCouvertAujourdhui()).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* L85 branch manquante : dateDebutCouverture != null mais today >= dateDebutCouverture
|
||||
* (today is NOT before → condition false)
|
||||
* → couvre la branche `dateDebutCouverture != null && today.isBefore(...) → false`
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("isCouvertAujourdhui: dateDebutCouverture non null mais pas avant → continue (branche false)")
|
||||
void isCouvertAujourdhui_debutNonNull_nonBefore_continueToActif() {
|
||||
AyantDroit a = new AyantDroit();
|
||||
a.setMembreOrganisation(newMembreOrganisation());
|
||||
a.setPrenom("X");
|
||||
a.setNom("Y");
|
||||
a.setLienParente(LienParente.ENFANT);
|
||||
// dateDebutCouverture est dans le passé → today.isBefore(debutCouverture) est false
|
||||
a.setDateDebutCouverture(LocalDate.now().minusDays(5));
|
||||
a.setDateFinCouverture(null);
|
||||
a.setActif(true);
|
||||
// Ne retourne pas false à la première condition → continue et retourne true (actif=true)
|
||||
assertThat(a.isCouvertAujourdhui()).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* L87 branch manquante : dateFinCouverture != null mais today <= dateFinCouverture
|
||||
* (today is NOT after → condition false)
|
||||
* → couvre la branche `dateFinCouverture != null && today.isAfter(...) → false`
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("isCouvertAujourdhui: dateFinCouverture non null mais pas encore dépassée → continue (branche false)")
|
||||
void isCouvertAujourdhui_finNonNull_nonAfter_continueToActif() {
|
||||
AyantDroit a = new AyantDroit();
|
||||
a.setMembreOrganisation(newMembreOrganisation());
|
||||
a.setPrenom("X");
|
||||
a.setNom("Y");
|
||||
a.setLienParente(LienParente.ENFANT);
|
||||
a.setDateDebutCouverture(LocalDate.now().minusDays(5));
|
||||
// dateFinCouverture dans le futur → today.isAfter(finCouverture) est false → continue
|
||||
a.setDateFinCouverture(LocalDate.now().plusDays(5));
|
||||
a.setActif(true);
|
||||
assertThat(a.isCouvertAujourdhui()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("BudgetLine")
|
||||
class BudgetLineTest {
|
||||
|
||||
private static Budget newBudget() {
|
||||
Budget b = new Budget();
|
||||
b.setId(UUID.randomUUID());
|
||||
return b;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getRealizationRate
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("getRealizationRate: amountPlanned == 0 renvoie 0.0")
|
||||
void getRealizationRate_zeroPlan_returns0() {
|
||||
BudgetLine line = new BudgetLine();
|
||||
line.setAmountPlanned(BigDecimal.ZERO);
|
||||
line.setAmountRealized(new BigDecimal("100.00"));
|
||||
assertThat(line.getRealizationRate()).isEqualTo(0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getRealizationRate: 75 réalisé / 100 prévu = 75%")
|
||||
void getRealizationRate_withValues_returnsRatio() {
|
||||
BudgetLine line = new BudgetLine();
|
||||
line.setAmountPlanned(new BigDecimal("100.00"));
|
||||
line.setAmountRealized(new BigDecimal("75.00"));
|
||||
assertThat(line.getRealizationRate()).isEqualTo(75.0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getVariance
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("getVariance: positif quand réalisé > prévu")
|
||||
void getVariance_positive_whenOver() {
|
||||
BudgetLine line = new BudgetLine();
|
||||
line.setAmountPlanned(new BigDecimal("100.00"));
|
||||
line.setAmountRealized(new BigDecimal("120.00"));
|
||||
assertThat(line.getVariance()).isEqualByComparingTo("20.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getVariance: négatif quand réalisé < prévu")
|
||||
void getVariance_negative_whenUnder() {
|
||||
BudgetLine line = new BudgetLine();
|
||||
line.setAmountPlanned(new BigDecimal("100.00"));
|
||||
line.setAmountRealized(new BigDecimal("80.00"));
|
||||
assertThat(line.getVariance()).isEqualByComparingTo("-20.00");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isOverBudget
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("isOverBudget: réalisé > prévu renvoie true")
|
||||
void isOverBudget_whenOver_returnsTrue() {
|
||||
BudgetLine line = new BudgetLine();
|
||||
line.setAmountPlanned(new BigDecimal("200.00"));
|
||||
line.setAmountRealized(new BigDecimal("201.00"));
|
||||
assertThat(line.isOverBudget()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isOverBudget: réalisé <= prévu renvoie false")
|
||||
void isOverBudget_whenNotOver_returnsFalse() {
|
||||
BudgetLine line = new BudgetLine();
|
||||
line.setAmountPlanned(new BigDecimal("200.00"));
|
||||
line.setAmountRealized(new BigDecimal("200.00"));
|
||||
assertThat(line.isOverBudget()).isFalse();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// onCreate (réflexion)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise amountRealized à ZERO si null")
|
||||
void onCreate_initializesAmountRealizedIfNull() throws Exception {
|
||||
BudgetLine line = new BudgetLine();
|
||||
line.setAmountRealized(null);
|
||||
|
||||
Method onCreate = BudgetLine.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(line);
|
||||
|
||||
assertThat(line.getAmountRealized()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: ne remplace pas amountRealized si déjà renseigné")
|
||||
void onCreate_doesNotOverrideAmountRealized() throws Exception {
|
||||
BudgetLine line = new BudgetLine();
|
||||
line.setAmountRealized(new BigDecimal("350.00"));
|
||||
|
||||
Method onCreate = BudgetLine.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(line);
|
||||
|
||||
assertThat(line.getAmountRealized()).isEqualByComparingTo("350.00");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// builder
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("builder: positionne tous les champs correctement")
|
||||
void builder_setsAllFields() {
|
||||
Budget budget = newBudget();
|
||||
|
||||
BudgetLine line = BudgetLine.builder()
|
||||
.budget(budget)
|
||||
.category("CONTRIBUTIONS")
|
||||
.name("Cotisations membres")
|
||||
.description("Cotisations mensuelles")
|
||||
.amountPlanned(new BigDecimal("50000.00"))
|
||||
.amountRealized(new BigDecimal("42000.00"))
|
||||
.notes("Léger retard de collecte")
|
||||
.build();
|
||||
|
||||
assertThat(line.getBudget()).isSameAs(budget);
|
||||
assertThat(line.getCategory()).isEqualTo("CONTRIBUTIONS");
|
||||
assertThat(line.getName()).isEqualTo("Cotisations membres");
|
||||
assertThat(line.getDescription()).isEqualTo("Cotisations mensuelles");
|
||||
assertThat(line.getAmountPlanned()).isEqualByComparingTo("50000.00");
|
||||
assertThat(line.getAmountRealized()).isEqualByComparingTo("42000.00");
|
||||
assertThat(line.getNotes()).isEqualTo("Léger retard de collecte");
|
||||
}
|
||||
}
|
||||
319
src/test/java/dev/lions/unionflow/server/entity/BudgetTest.java
Normal file
319
src/test/java/dev/lions/unionflow/server/entity/BudgetTest.java
Normal file
@@ -0,0 +1,319 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("Budget")
|
||||
class BudgetTest {
|
||||
|
||||
private static Organisation newOrganisation() {
|
||||
Organisation o = new Organisation();
|
||||
o.setId(UUID.randomUUID());
|
||||
return o;
|
||||
}
|
||||
|
||||
private static BudgetLine newLine(BigDecimal planned, BigDecimal realized) {
|
||||
BudgetLine line = new BudgetLine();
|
||||
line.setAmountPlanned(planned);
|
||||
line.setAmountRealized(realized);
|
||||
return line;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getRealizationRate
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("getRealizationRate: totalPlanned == 0 renvoie 0.0")
|
||||
void getRealizationRate_zeroPlan_returns0() {
|
||||
Budget b = new Budget();
|
||||
b.setTotalPlanned(BigDecimal.ZERO);
|
||||
b.setTotalRealized(new BigDecimal("500.00"));
|
||||
assertThat(b.getRealizationRate()).isEqualTo(0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getRealizationRate: 500 réalisé / 1000 prévu = 50%")
|
||||
void getRealizationRate_withPlan_returnsRatio() {
|
||||
Budget b = new Budget();
|
||||
b.setTotalPlanned(new BigDecimal("1000.00"));
|
||||
b.setTotalRealized(new BigDecimal("500.00"));
|
||||
assertThat(b.getRealizationRate()).isEqualTo(50.0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getVariance
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("getVariance: renvoie réalisé - prévu")
|
||||
void getVariance_returnsRealized_minus_planned() {
|
||||
Budget b = new Budget();
|
||||
b.setTotalPlanned(new BigDecimal("1000.00"));
|
||||
b.setTotalRealized(new BigDecimal("1200.00"));
|
||||
assertThat(b.getVariance()).isEqualByComparingTo("200.00");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isOverBudget
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("isOverBudget: réalisé > prévu renvoie true")
|
||||
void isOverBudget_whenOver_returnsTrue() {
|
||||
Budget b = new Budget();
|
||||
b.setTotalPlanned(new BigDecimal("1000.00"));
|
||||
b.setTotalRealized(new BigDecimal("1001.00"));
|
||||
assertThat(b.isOverBudget()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isOverBudget: réalisé == prévu renvoie false")
|
||||
void isOverBudget_whenEqual_returnsFalse() {
|
||||
Budget b = new Budget();
|
||||
b.setTotalPlanned(new BigDecimal("1000.00"));
|
||||
b.setTotalRealized(new BigDecimal("1000.00"));
|
||||
assertThat(b.isOverBudget()).isFalse();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isActive
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("isActive: statut ACTIVE renvoie true")
|
||||
void isActive_active_returnsTrue() {
|
||||
Budget b = new Budget();
|
||||
b.setStatus("ACTIVE");
|
||||
assertThat(b.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActive: statut DRAFT renvoie false")
|
||||
void isActive_draft_returnsFalse() {
|
||||
Budget b = new Budget();
|
||||
b.setStatus("DRAFT");
|
||||
assertThat(b.isActive()).isFalse();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isCurrentPeriod
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("isCurrentPeriod: aujourd'hui dans la période renvoie true")
|
||||
void isCurrentPeriod_duringPeriod_returnsTrue() {
|
||||
Budget b = new Budget();
|
||||
b.setStartDate(LocalDate.now().minusDays(5));
|
||||
b.setEndDate(LocalDate.now().plusDays(5));
|
||||
assertThat(b.isCurrentPeriod()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isCurrentPeriod: période terminée renvoie false")
|
||||
void isCurrentPeriod_afterPeriod_returnsFalse() {
|
||||
Budget b = new Budget();
|
||||
b.setStartDate(LocalDate.now().minusDays(10));
|
||||
b.setEndDate(LocalDate.now().minusDays(1));
|
||||
assertThat(b.isCurrentPeriod()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isCurrentPeriod: période pas encore commencée renvoie false")
|
||||
void isCurrentPeriod_beforePeriod_returnsFalse() {
|
||||
Budget b = new Budget();
|
||||
b.setStartDate(LocalDate.now().plusDays(1));
|
||||
b.setEndDate(LocalDate.now().plusDays(10));
|
||||
assertThat(b.isCurrentPeriod()).isFalse();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// addLine / removeLine / recalculateTotals
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("addLine: ajoute la ligne, lie le budget parent et recalcule les totaux")
|
||||
void addLine_addsLineAndRecalculates() {
|
||||
Budget b = Budget.builder()
|
||||
.name("Budget Test")
|
||||
.organisation(newOrganisation())
|
||||
.period("ANNUAL")
|
||||
.year(2026)
|
||||
.status("DRAFT")
|
||||
.createdById(UUID.randomUUID())
|
||||
.createdAtBudget(LocalDateTime.now())
|
||||
.startDate(LocalDate.of(2026, 1, 1))
|
||||
.endDate(LocalDate.of(2026, 12, 31))
|
||||
.build();
|
||||
|
||||
BudgetLine line = newLine(new BigDecimal("500.00"), new BigDecimal("200.00"));
|
||||
b.addLine(line);
|
||||
|
||||
assertThat(b.getLines()).hasSize(1);
|
||||
assertThat(line.getBudget()).isSameAs(b);
|
||||
assertThat(b.getTotalPlanned()).isEqualByComparingTo("500.00");
|
||||
assertThat(b.getTotalRealized()).isEqualByComparingTo("200.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("removeLine: retire la ligne, délie le budget parent et recalcule les totaux")
|
||||
void removeLine_removesAndRecalculates() {
|
||||
Budget b = Budget.builder()
|
||||
.name("Budget Test")
|
||||
.organisation(newOrganisation())
|
||||
.period("ANNUAL")
|
||||
.year(2026)
|
||||
.status("DRAFT")
|
||||
.createdById(UUID.randomUUID())
|
||||
.createdAtBudget(LocalDateTime.now())
|
||||
.startDate(LocalDate.of(2026, 1, 1))
|
||||
.endDate(LocalDate.of(2026, 12, 31))
|
||||
.build();
|
||||
|
||||
BudgetLine line1 = newLine(new BigDecimal("300.00"), new BigDecimal("100.00"));
|
||||
BudgetLine line2 = newLine(new BigDecimal("200.00"), new BigDecimal("50.00"));
|
||||
b.addLine(line1);
|
||||
b.addLine(line2);
|
||||
assertThat(b.getLines()).hasSize(2);
|
||||
|
||||
b.removeLine(line1);
|
||||
|
||||
assertThat(b.getLines()).hasSize(1);
|
||||
assertThat(line1.getBudget()).isNull();
|
||||
assertThat(b.getTotalPlanned()).isEqualByComparingTo("200.00");
|
||||
assertThat(b.getTotalRealized()).isEqualByComparingTo("50.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("recalculateTotals: somme correcte avec plusieurs lignes")
|
||||
void recalculateTotals_multipleLines_sumsCorrectly() {
|
||||
Budget b = Budget.builder()
|
||||
.name("Budget Multi")
|
||||
.organisation(newOrganisation())
|
||||
.period("ANNUAL")
|
||||
.year(2026)
|
||||
.status("DRAFT")
|
||||
.createdById(UUID.randomUUID())
|
||||
.createdAtBudget(LocalDateTime.now())
|
||||
.startDate(LocalDate.of(2026, 1, 1))
|
||||
.endDate(LocalDate.of(2026, 12, 31))
|
||||
.build();
|
||||
|
||||
b.addLine(newLine(new BigDecimal("100.00"), new BigDecimal("80.00")));
|
||||
b.addLine(newLine(new BigDecimal("200.00"), new BigDecimal("150.00")));
|
||||
b.addLine(newLine(new BigDecimal("300.00"), new BigDecimal("320.00")));
|
||||
|
||||
assertThat(b.getTotalPlanned()).isEqualByComparingTo("600.00");
|
||||
assertThat(b.getTotalRealized()).isEqualByComparingTo("550.00");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// onCreate (réflexion)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise les champs null (createdAtBudget, currency, status, totalPlanned, totalRealized)")
|
||||
void onCreate_initializesNullFields() throws Exception {
|
||||
Budget b = new Budget();
|
||||
b.setCreatedAtBudget(null);
|
||||
b.setCurrency(null);
|
||||
b.setStatus(null);
|
||||
b.setTotalPlanned(null);
|
||||
b.setTotalRealized(null);
|
||||
|
||||
Method onCreate = Budget.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(b);
|
||||
|
||||
assertThat(b.getCreatedAtBudget()).isNotNull();
|
||||
assertThat(b.getCurrency()).isEqualTo("XOF");
|
||||
assertThat(b.getStatus()).isEqualTo("DRAFT");
|
||||
assertThat(b.getTotalPlanned()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
assertThat(b.getTotalRealized()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: ne remplace pas les champs déjà renseignés")
|
||||
void onCreate_doesNotOverrideExistingFields() throws Exception {
|
||||
LocalDateTime existingDate = LocalDateTime.of(2025, 6, 15, 10, 0);
|
||||
Budget b = new Budget();
|
||||
b.setCreatedAtBudget(existingDate);
|
||||
b.setCurrency("EUR");
|
||||
b.setStatus("ACTIVE");
|
||||
b.setTotalPlanned(new BigDecimal("5000.00"));
|
||||
b.setTotalRealized(new BigDecimal("2500.00"));
|
||||
|
||||
Method onCreate = Budget.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(b);
|
||||
|
||||
assertThat(b.getCreatedAtBudget()).isEqualTo(existingDate);
|
||||
assertThat(b.getCurrency()).isEqualTo("EUR");
|
||||
assertThat(b.getStatus()).isEqualTo("ACTIVE");
|
||||
assertThat(b.getTotalPlanned()).isEqualByComparingTo("5000.00");
|
||||
assertThat(b.getTotalRealized()).isEqualByComparingTo("2500.00");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// builder
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("builder: crée un objet complet avec tous les champs")
|
||||
void builder_createsComplete() {
|
||||
UUID createdById = UUID.randomUUID();
|
||||
UUID approvedById = UUID.randomUUID();
|
||||
Organisation org = newOrganisation();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime approvedAt = now.plusDays(1);
|
||||
LocalDate start = LocalDate.of(2026, 1, 1);
|
||||
LocalDate end = LocalDate.of(2026, 12, 31);
|
||||
|
||||
Budget b = Budget.builder()
|
||||
.name("Budget Annuel 2026")
|
||||
.description("Description du budget")
|
||||
.organisation(org)
|
||||
.period("ANNUAL")
|
||||
.year(2026)
|
||||
.month(null)
|
||||
.status("ACTIVE")
|
||||
.totalPlanned(new BigDecimal("1000000.00"))
|
||||
.totalRealized(new BigDecimal("500000.00"))
|
||||
.currency("XOF")
|
||||
.createdById(createdById)
|
||||
.createdAtBudget(now)
|
||||
.approvedAt(approvedAt)
|
||||
.approvedById(approvedById)
|
||||
.startDate(start)
|
||||
.endDate(end)
|
||||
.metadata("{\"note\":\"test\"}")
|
||||
.build();
|
||||
|
||||
assertThat(b.getName()).isEqualTo("Budget Annuel 2026");
|
||||
assertThat(b.getDescription()).isEqualTo("Description du budget");
|
||||
assertThat(b.getOrganisation()).isSameAs(org);
|
||||
assertThat(b.getPeriod()).isEqualTo("ANNUAL");
|
||||
assertThat(b.getYear()).isEqualTo(2026);
|
||||
assertThat(b.getMonth()).isNull();
|
||||
assertThat(b.getStatus()).isEqualTo("ACTIVE");
|
||||
assertThat(b.getTotalPlanned()).isEqualByComparingTo("1000000.00");
|
||||
assertThat(b.getTotalRealized()).isEqualByComparingTo("500000.00");
|
||||
assertThat(b.getCurrency()).isEqualTo("XOF");
|
||||
assertThat(b.getCreatedById()).isEqualTo(createdById);
|
||||
assertThat(b.getCreatedAtBudget()).isEqualTo(now);
|
||||
assertThat(b.getApprovedAt()).isEqualTo(approvedAt);
|
||||
assertThat(b.getApprovedById()).isEqualTo(approvedById);
|
||||
assertThat(b.getStartDate()).isEqualTo(start);
|
||||
assertThat(b.getEndDate()).isEqualTo(end);
|
||||
assertThat(b.getMetadata()).isEqualTo("{\"note\":\"test\"}");
|
||||
assertThat(b.getLines()).isNotNull().isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -89,4 +89,38 @@ class CompteWaveTest {
|
||||
c.setNumeroTelephone("+22507000005");
|
||||
assertThat(c.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: environnement null → SANDBOX")
|
||||
void onCreate_environnementNull_defaultsSandbox() {
|
||||
CompteWave c = new CompteWave();
|
||||
c.setStatutCompte(null);
|
||||
c.setEnvironnement(null);
|
||||
c.onCreate();
|
||||
assertThat(c.getEnvironnement()).isEqualTo("SANDBOX");
|
||||
assertThat(c.getStatutCompte()).isEqualTo(StatutCompteWave.NON_VERIFIE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: environnement vide → SANDBOX")
|
||||
void onCreate_environnementEmpty_defaultsSandbox() {
|
||||
CompteWave c = new CompteWave();
|
||||
c.setEnvironnement("");
|
||||
c.onCreate();
|
||||
assertThat(c.getEnvironnement()).isEqualTo("SANDBOX");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: statutCompte et environnement déjà définis → conservés (branches false)")
|
||||
void onCreate_allDejaDefinis_conservesValues() {
|
||||
// Valeurs déjà définies → conservées sans modification
|
||||
CompteWave c = new CompteWave();
|
||||
c.setStatutCompte(StatutCompteWave.VERIFIE);
|
||||
c.setEnvironnement("PRODUCTION");
|
||||
|
||||
c.onCreate();
|
||||
|
||||
assertThat(c.getStatutCompte()).isEqualTo(StatutCompteWave.VERIFIE);
|
||||
assertThat(c.getEnvironnement()).isEqualTo("PRODUCTION");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,43 @@ class ConfigurationWaveTest {
|
||||
assertThat(c.getEnvironnement()).isEqualTo("COMMON");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate initialise typeValeur et environnement si vide")
|
||||
void onCreate_initialiseChamps_empty() throws Exception {
|
||||
ConfigurationWave c = new ConfigurationWave();
|
||||
c.setCle("k2");
|
||||
c.setTypeValeur("");
|
||||
c.setEnvironnement("");
|
||||
Method onCreate = ConfigurationWave.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(c);
|
||||
assertThat(c.getTypeValeur()).isEqualTo("STRING");
|
||||
assertThat(c.getEnvironnement()).isEqualTo("COMMON");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: typeValeur et environnement déjà renseignés → non écrasés")
|
||||
void onCreate_existingValues_notOverwritten() throws Exception {
|
||||
ConfigurationWave c = new ConfigurationWave();
|
||||
c.setCle("k3");
|
||||
c.setTypeValeur("NUMBER");
|
||||
c.setEnvironnement("PRODUCTION");
|
||||
Method onCreate = ConfigurationWave.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(c);
|
||||
assertThat(c.getTypeValeur()).isEqualTo("NUMBER");
|
||||
assertThat(c.getEnvironnement()).isEqualTo("PRODUCTION");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isEncryptee: false si typeValeur null")
|
||||
void isEncryptee_nullTypeValeur_returnsFalse() {
|
||||
ConfigurationWave c = new ConfigurationWave();
|
||||
c.setCle("x");
|
||||
c.setTypeValeur(null);
|
||||
assertThat(c.isEncryptee()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("equals et hashCode")
|
||||
void equalsHashCode() {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.communication.ConversationType;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
@DisplayName("Conversation")
|
||||
class ConversationTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("getters/setters de base")
|
||||
void gettersSetters() {
|
||||
Conversation c = new Conversation();
|
||||
c.setName("Groupe Test");
|
||||
c.setDescription("Description groupe");
|
||||
c.setType(ConversationType.GROUP);
|
||||
c.setIsMuted(false);
|
||||
c.setIsPinned(true);
|
||||
c.setIsArchived(false);
|
||||
|
||||
assertThat(c.getName()).isEqualTo("Groupe Test");
|
||||
assertThat(c.getDescription()).isEqualTo("Description groupe");
|
||||
assertThat(c.getType()).isEqualTo(ConversationType.GROUP);
|
||||
assertThat(c.getIsMuted()).isFalse();
|
||||
assertThat(c.getIsPinned()).isTrue();
|
||||
assertThat(c.getIsArchived()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onUpdate (PreUpdate) - met à jour updatedAt via réflexion")
|
||||
void onUpdate_setsUpdatedAt() throws Exception {
|
||||
Conversation c = new Conversation();
|
||||
assertThat(c.getUpdatedAt()).isNull();
|
||||
|
||||
Method onUpdate = Conversation.class.getDeclaredMethod("onUpdate");
|
||||
onUpdate.setAccessible(true);
|
||||
|
||||
LocalDateTime before = LocalDateTime.now().minusSeconds(1);
|
||||
onUpdate.invoke(c);
|
||||
LocalDateTime after = LocalDateTime.now().plusSeconds(1);
|
||||
|
||||
assertThat(c.getUpdatedAt()).isNotNull();
|
||||
assertThat(c.getUpdatedAt()).isAfter(before);
|
||||
assertThat(c.getUpdatedAt()).isBefore(after);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onUpdate appelé deux fois met à jour updatedAt à chaque fois")
|
||||
void onUpdate_calledTwice_updatesEachTime() throws Exception {
|
||||
Conversation c = new Conversation();
|
||||
|
||||
Method onUpdate = Conversation.class.getDeclaredMethod("onUpdate");
|
||||
onUpdate.setAccessible(true);
|
||||
|
||||
onUpdate.invoke(c);
|
||||
LocalDateTime first = c.getUpdatedAt();
|
||||
|
||||
// petit délai pour différencier les timestamps
|
||||
Thread.sleep(5);
|
||||
|
||||
onUpdate.invoke(c);
|
||||
LocalDateTime second = c.getUpdatedAt();
|
||||
|
||||
assertThat(second).isAfterOrEqualTo(first);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("participants initialisé à liste vide")
|
||||
void participants_initializedEmpty() {
|
||||
Conversation c = new Conversation();
|
||||
assertThat(c.getParticipants()).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("messages initialisé à liste vide")
|
||||
void messages_initializedEmpty() {
|
||||
Conversation c = new Conversation();
|
||||
assertThat(c.getMessages()).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isMuted et isPinned et isArchived défaut false")
|
||||
void defaultFlags_areFalse() {
|
||||
Conversation c = new Conversation();
|
||||
assertThat(c.getIsMuted()).isFalse();
|
||||
assertThat(c.getIsPinned()).isFalse();
|
||||
assertThat(c.getIsArchived()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -142,4 +142,79 @@ class CotisationTest {
|
||||
c.setAnnee(2025);
|
||||
assertThat(c.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getMontantRestant: ZERO si montantDu null")
|
||||
void getMontantRestant_montantDuNull_returnsZero() {
|
||||
Cotisation c = new Cotisation();
|
||||
c.setMontantPaye(new BigDecimal("50.00"));
|
||||
// montantDu = null
|
||||
assertThat(c.getMontantRestant()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isEnRetard: false si dateEcheance null")
|
||||
void isEnRetard_echeanceNull_returnsFalse() {
|
||||
Cotisation c = new Cotisation();
|
||||
c.setMontantDu(new BigDecimal("100.00"));
|
||||
c.setMontantPaye(BigDecimal.ZERO);
|
||||
// dateEcheance = null
|
||||
assertThat(c.isEnRetard()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: numeroReference déjà défini → conservé (branche false)")
|
||||
void onCreate_numeroReferenceDejaDefini_conserve() {
|
||||
// numeroReference déjà défini → non écrasé par onCreate
|
||||
Cotisation c = new Cotisation();
|
||||
c.setNumeroReference("COT-EXISTANT-001");
|
||||
c.setCodeDevise("XOF");
|
||||
c.setStatut("EN_ATTENTE");
|
||||
c.setMontantPaye(BigDecimal.ZERO);
|
||||
c.setNombreRappels(0);
|
||||
c.setRecurrente(false);
|
||||
|
||||
c.onCreate();
|
||||
|
||||
// numeroReference doit rester inchangé
|
||||
assertThat(c.getNumeroReference()).isEqualTo("COT-EXISTANT-001");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: numeroReference vide (empty string) → généré (branche isEmpty)")
|
||||
void onCreate_emptyNumeroReference_generated() throws Exception {
|
||||
Cotisation c = new Cotisation();
|
||||
c.setNumeroReference(""); // non null mais vide → isEmpty() est true → doit générer
|
||||
c.setCodeDevise("XOF");
|
||||
c.setStatut("EN_ATTENTE");
|
||||
c.setMontantPaye(BigDecimal.ZERO);
|
||||
c.setNombreRappels(0);
|
||||
c.setRecurrente(false);
|
||||
|
||||
java.lang.reflect.Method onCreate = Cotisation.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(c);
|
||||
|
||||
assertThat(c.getNumeroReference()).isNotEmpty().startsWith("COT-");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise les défauts si null")
|
||||
void onCreate_setsDefaults() {
|
||||
Cotisation c = new Cotisation();
|
||||
// Force null pour couvrir toutes les branches de initialisation
|
||||
c.setNumeroReference(null);
|
||||
c.setCodeDevise(null);
|
||||
c.setStatut(null);
|
||||
c.setMontantPaye(null);
|
||||
c.setNombreRappels(null);
|
||||
c.setRecurrente(null);
|
||||
c.onCreate();
|
||||
assertThat(c.getNumeroReference()).isNotNull().startsWith("COT-");
|
||||
assertThat(c.getCodeDevise()).isEqualTo("XOF");
|
||||
assertThat(c.getStatut()).isEqualTo("EN_ATTENTE");
|
||||
assertThat(c.getMontantPaye()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
assertThat(c.getNombreRappels()).isEqualTo(0);
|
||||
assertThat(c.getRecurrente()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests de couverture complémentaires pour DemandeAdhesion.
|
||||
* Couvre les branches manquantes de isPayeeIntegralement().
|
||||
*/
|
||||
@DisplayName("DemandeAdhesion - couverture complémentaire")
|
||||
class DemandeAdhesionCoverageTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("isPayeeIntegralement retourne false quand montantPaye est null")
|
||||
void isPayeeIntegralement_nullMontantPaye_returnsFalse() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setFraisAdhesion(new BigDecimal("5000.00"));
|
||||
d.setMontantPaye(null);
|
||||
assertThat(d.isPayeeIntegralement()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPayeeIntegralement retourne true quand montantPaye > fraisAdhesion (overpayment)")
|
||||
void isPayeeIntegralement_overpayment_returnsTrue() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setFraisAdhesion(new BigDecimal("5000.00"));
|
||||
d.setMontantPaye(new BigDecimal("6000.00"));
|
||||
assertThat(d.isPayeeIntegralement()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPayeeIntegralement retourne true quand fraisAdhesion est zero et montantPaye est zero")
|
||||
void isPayeeIntegralement_zeroFraisZeroMontant_returnsTrue() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setFraisAdhesion(BigDecimal.ZERO);
|
||||
d.setMontantPaye(BigDecimal.ZERO);
|
||||
assertThat(d.isPayeeIntegralement()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPayeeIntegralement retourne false quand les deux champs sont null")
|
||||
void isPayeeIntegralement_bothNull_returnsFalse() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setFraisAdhesion(null);
|
||||
d.setMontantPaye(null);
|
||||
assertThat(d.isPayeeIntegralement()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isEnAttente retourne false pour statut ANNULEE")
|
||||
void isEnAttente_annulee_returnsFalse() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setStatut("ANNULEE");
|
||||
assertThat(d.isEnAttente()).isFalse();
|
||||
assertThat(d.isApprouvee()).isFalse();
|
||||
assertThat(d.isRejetee()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -91,4 +91,124 @@ class DemandeAdhesionTest {
|
||||
d.setOrganisation(newOrganisation());
|
||||
assertThat(d.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isApprouvee retourne true pour statut APPROUVEE")
|
||||
void isApprouvee_approuvee_returnsTrue() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setStatut("APPROUVEE");
|
||||
assertThat(d.isApprouvee()).isTrue();
|
||||
assertThat(d.isRejetee()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isRejetee retourne true pour statut REJETEE")
|
||||
void isRejetee_rejetee_returnsTrue() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setStatut("REJETEE");
|
||||
assertThat(d.isRejetee()).isTrue();
|
||||
assertThat(d.isApprouvee()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("genererNumeroReference retourne un code non vide")
|
||||
void genererNumeroReference_returnsNonEmpty() {
|
||||
String ref = DemandeAdhesion.genererNumeroReference();
|
||||
assertThat(ref).isNotNull().isNotEmpty().startsWith("ADH-");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise tous les défauts si null")
|
||||
void onCreate_setsDefaults() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
// Les champs @Builder.Default sont déjà initialisés — les forcer à null pour tester les branches true
|
||||
d.setDateDemande(null);
|
||||
d.setStatut(null);
|
||||
d.setCodeDevise(null);
|
||||
d.setFraisAdhesion(null);
|
||||
d.setMontantPaye(null);
|
||||
// numeroReference est null par défaut (pas de @Builder.Default)
|
||||
d.onCreate();
|
||||
assertThat(d.getDateDemande()).isNotNull();
|
||||
assertThat(d.getStatut()).isEqualTo("EN_ATTENTE");
|
||||
assertThat(d.getCodeDevise()).isEqualTo("XOF");
|
||||
assertThat(d.getFraisAdhesion()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
assertThat(d.getMontantPaye()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
assertThat(d.getNumeroReference()).isNotNull().startsWith("ADH-");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: ne modifie pas les champs déjà initialisés")
|
||||
void onCreate_preservesExistingValues() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
LocalDateTime specificDate = LocalDateTime.of(2024, 1, 15, 10, 30);
|
||||
d.setDateDemande(specificDate);
|
||||
d.setStatut("APPROUVEE");
|
||||
d.setCodeDevise("EUR");
|
||||
d.setFraisAdhesion(BigDecimal.valueOf(100));
|
||||
d.setMontantPaye(BigDecimal.valueOf(50));
|
||||
d.setNumeroReference("ADH-CUSTOM-001");
|
||||
|
||||
d.onCreate();
|
||||
|
||||
assertThat(d.getDateDemande()).isEqualTo(specificDate);
|
||||
assertThat(d.getStatut()).isEqualTo("APPROUVEE");
|
||||
assertThat(d.getCodeDevise()).isEqualTo("EUR");
|
||||
assertThat(d.getFraisAdhesion()).isEqualByComparingTo(BigDecimal.valueOf(100));
|
||||
assertThat(d.getMontantPaye()).isEqualByComparingTo(BigDecimal.valueOf(50));
|
||||
assertThat(d.getNumeroReference()).isEqualTo("ADH-CUSTOM-001");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: génère une référence si numeroReference est vide")
|
||||
void onCreate_emptyReference_generatesNew() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setNumeroReference(""); // vide → condition isEmpty() == true
|
||||
d.setStatut("EN_ATTENTE");
|
||||
d.setCodeDevise("XOF");
|
||||
d.setFraisAdhesion(BigDecimal.ZERO);
|
||||
d.setMontantPaye(BigDecimal.ZERO);
|
||||
d.setDateDemande(LocalDateTime.now());
|
||||
|
||||
d.onCreate();
|
||||
|
||||
assertThat(d.getNumeroReference()).isNotEmpty().startsWith("ADH-");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isEnAttente retourne true pour statut EN_ATTENTE")
|
||||
void isEnAttente_retournsTrue() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setStatut("EN_ATTENTE");
|
||||
assertThat(d.isEnAttente()).isTrue();
|
||||
assertThat(d.isApprouvee()).isFalse();
|
||||
assertThat(d.isRejetee()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPayeeIntegralement retourne true quand montantPaye >= fraisAdhesion")
|
||||
void isPayeeIntegralement_paymentComplete_returnsTrue() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setFraisAdhesion(new BigDecimal("5000.00"));
|
||||
d.setMontantPaye(new BigDecimal("5000.00"));
|
||||
assertThat(d.isPayeeIntegralement()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPayeeIntegralement retourne false quand montantPaye < fraisAdhesion")
|
||||
void isPayeeIntegralement_paymentIncomplete_returnsFalse() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setFraisAdhesion(new BigDecimal("5000.00"));
|
||||
d.setMontantPaye(new BigDecimal("2500.00"));
|
||||
assertThat(d.isPayeeIntegralement()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPayeeIntegralement retourne false quand fraisAdhesion est null")
|
||||
void isPayeeIntegralement_nullFrais_returnsFalse() {
|
||||
DemandeAdhesion d = new DemandeAdhesion();
|
||||
d.setFraisAdhesion(null);
|
||||
d.setMontantPaye(BigDecimal.ZERO);
|
||||
assertThat(d.isPayeeIntegralement()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,14 @@ class DocumentTest {
|
||||
assertThat(d.verifierIntegriteSha256("autre")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("verifierIntegriteSha256: false si hashSha256 null")
|
||||
void verifierIntegriteSha256_hashNull_returnsFalse() {
|
||||
Document d = new Document();
|
||||
d.setHashSha256(null);
|
||||
assertThat(d.verifierIntegriteSha256("def456")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getTailleFormatee: B, KB, MB")
|
||||
void getTailleFormatee() {
|
||||
|
||||
@@ -4,10 +4,13 @@ import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("EcritureComptable")
|
||||
@@ -122,4 +125,228 @@ class EcritureComptableTest {
|
||||
e.setJournal(newJournal());
|
||||
assertThat(e.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onUpdate (PreUpdate) calcule les totaux via réflexion")
|
||||
void onUpdate_calculesTotaux() throws Exception {
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece("X");
|
||||
e.setDateEcriture(LocalDate.now());
|
||||
e.setLibelle("L");
|
||||
e.setJournal(newJournal());
|
||||
e.setMontantDebit(BigDecimal.ZERO);
|
||||
e.setMontantCredit(BigDecimal.ZERO);
|
||||
|
||||
LigneEcriture l1 = new LigneEcriture();
|
||||
l1.setMontantDebit(new BigDecimal("200"));
|
||||
l1.setMontantCredit(BigDecimal.ZERO);
|
||||
LigneEcriture l2 = new LigneEcriture();
|
||||
l2.setMontantDebit(BigDecimal.ZERO);
|
||||
l2.setMontantCredit(new BigDecimal("200"));
|
||||
e.getLignes().add(l1);
|
||||
e.getLignes().add(l2);
|
||||
|
||||
Method onUpdate = EcritureComptable.class.getDeclaredMethod("onUpdate");
|
||||
onUpdate.setAccessible(true);
|
||||
onUpdate.invoke(e);
|
||||
|
||||
assertThat(e.getMontantDebit()).isEqualByComparingTo("200");
|
||||
assertThat(e.getMontantCredit()).isEqualByComparingTo("200");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise les défauts si null")
|
||||
void onCreate_setsDefaults() throws Exception {
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece(null);
|
||||
e.setDateEcriture(null);
|
||||
e.setMontantDebit(null);
|
||||
e.setMontantCredit(null);
|
||||
e.setPointe(null);
|
||||
|
||||
Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(e);
|
||||
|
||||
assertThat(e.getNumeroPiece()).isNotNull().startsWith("ECR-");
|
||||
assertThat(e.getDateEcriture()).isEqualTo(LocalDate.now());
|
||||
assertThat(e.getMontantDebit()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
assertThat(e.getMontantCredit()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
assertThat(e.getPointe()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: numeroPiece vide → généré")
|
||||
void onCreate_numeroPieceEmpty_generated() throws Exception {
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece("");
|
||||
e.setDateEcriture(LocalDate.of(2025, 6, 1));
|
||||
|
||||
Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(e);
|
||||
|
||||
assertThat(e.getNumeroPiece()).isNotEmpty().startsWith("ECR-20250601-");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: avec lignes → calculerTotaux appelé")
|
||||
void onCreate_withLignes_calculesTotaux() throws Exception {
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece("X");
|
||||
e.setDateEcriture(LocalDate.now());
|
||||
LigneEcriture l1 = new LigneEcriture();
|
||||
l1.setMontantDebit(new BigDecimal("300"));
|
||||
l1.setMontantCredit(BigDecimal.ZERO);
|
||||
LigneEcriture l2 = new LigneEcriture();
|
||||
l2.setMontantDebit(BigDecimal.ZERO);
|
||||
l2.setMontantCredit(new BigDecimal("300"));
|
||||
e.getLignes().add(l1);
|
||||
e.getLignes().add(l2);
|
||||
|
||||
Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(e);
|
||||
|
||||
assertThat(e.getMontantDebit()).isEqualByComparingTo("300");
|
||||
assertThat(e.getMontantCredit()).isEqualByComparingTo("300");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isEquilibree: false si montantCredit null (branche ||)")
|
||||
void isEquilibree_montantCreditNull_returnsFalse() {
|
||||
// montantDebit non null mais montantCredit null → retourne false
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setJournal(newJournal());
|
||||
e.setNumeroPiece("X");
|
||||
e.setDateEcriture(LocalDate.now());
|
||||
e.setLibelle("L");
|
||||
e.setMontantDebit(new BigDecimal("100.00"));
|
||||
e.setMontantCredit(null);
|
||||
assertThat(e.isEquilibree()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotaux: lignes vides → totaux à ZERO")
|
||||
void calculerTotaux_emptyLignes_setsZero() {
|
||||
// Couvre la branche : `if (lignes == null || lignes.isEmpty()) { return ZERO; }`
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setJournal(newJournal());
|
||||
e.setNumeroPiece("X");
|
||||
e.setDateEcriture(LocalDate.now());
|
||||
e.setLibelle("L");
|
||||
e.setMontantDebit(new BigDecimal("500.00"));
|
||||
e.setMontantCredit(new BigDecimal("500.00"));
|
||||
// lignes est vide (défaut)
|
||||
e.calculerTotaux();
|
||||
assertThat(e.getMontantDebit()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
assertThat(e.getMontantCredit()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: lignes non vides présentes → calculerTotaux() appelé (branche true)")
|
||||
void onCreate_lignesNonVides_calculeTotaux() throws Exception {
|
||||
// Couvre la branche `if (lignes != null && !lignes.isEmpty())` → true dans onCreate
|
||||
// Ce test est complémentaire à onCreate_withLignes_calculesTotaux pour s'assurer
|
||||
// que le chemin est bien couvert.
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece("PIECE-001");
|
||||
e.setDateEcriture(LocalDate.now());
|
||||
e.setLibelle("Test");
|
||||
LigneEcriture l1 = new LigneEcriture();
|
||||
l1.setMontantDebit(new BigDecimal("75"));
|
||||
l1.setMontantCredit(BigDecimal.ZERO);
|
||||
LigneEcriture l2 = new LigneEcriture();
|
||||
l2.setMontantDebit(BigDecimal.ZERO);
|
||||
l2.setMontantCredit(new BigDecimal("75"));
|
||||
e.getLignes().add(l1);
|
||||
e.getLignes().add(l2);
|
||||
|
||||
Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(e);
|
||||
|
||||
assertThat(e.getMontantDebit()).isEqualByComparingTo("75");
|
||||
assertThat(e.getMontantCredit()).isEqualByComparingTo("75");
|
||||
// numeroPiece déjà défini → conservé (branche false du if numeroPiece)
|
||||
assertThat(e.getNumeroPiece()).isEqualTo("PIECE-001");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotaux: lignes == null → totaux à ZERO (branche lignes null)")
|
||||
void calculerTotaux_nullLignes_setsZero() throws Exception {
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece("X");
|
||||
e.setDateEcriture(LocalDate.now());
|
||||
e.setLibelle("L");
|
||||
e.setMontantDebit(new BigDecimal("999"));
|
||||
e.setMontantCredit(new BigDecimal("999"));
|
||||
// Forcer lignes à null via réflexion
|
||||
java.lang.reflect.Field lignesField = EcritureComptable.class.getDeclaredField("lignes");
|
||||
lignesField.setAccessible(true);
|
||||
lignesField.set(e, null);
|
||||
e.calculerTotaux();
|
||||
assertThat(e.getMontantDebit()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
assertThat(e.getMontantCredit()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: lignes != null et isEmpty → calculerTotaux NON appelé (branche false)")
|
||||
void onCreate_lignesEmptyList_calculerTotauxNotCalled() throws Exception {
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece("X");
|
||||
e.setDateEcriture(LocalDate.now());
|
||||
e.setLibelle("L");
|
||||
e.setMontantDebit(new BigDecimal("10"));
|
||||
e.setMontantCredit(new BigDecimal("10"));
|
||||
// lignes est une ArrayList vide (valeur par défaut) → lignes != null && !lignes.isEmpty() → false
|
||||
assertThat(e.getLignes()).isEmpty();
|
||||
Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(e);
|
||||
// calculerTotaux n'a pas été appelé → montants restent (ou reset à 0 par init)
|
||||
// but montantDebit/Credit were already set, and they won't be recalculated
|
||||
// Just verify no exception and onCreate ran
|
||||
assertThat(e.getNumeroPiece()).isEqualTo("X");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: numeroPiece vide ('') → génère un nouveau numeroPiece (branche isEmpty() true)")
|
||||
void onCreate_numeroPieceEmpty_generatesPiece() throws Exception {
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece(""); // non-null mais vide → isEmpty() true → génère
|
||||
e.setDateEcriture(LocalDate.now()); // non-null → ternaire true
|
||||
e.setLibelle("L");
|
||||
e.setMontantDebit(new BigDecimal("10"));
|
||||
e.setMontantCredit(new BigDecimal("10"));
|
||||
|
||||
Method onCreate = EcritureComptable.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(e);
|
||||
|
||||
// numeroPiece était vide → a été généré
|
||||
assertThat(e.getNumeroPiece()).isNotEmpty();
|
||||
assertThat(e.getNumeroPiece()).startsWith("ECR");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotaux: filtre les montants null (branch false des lambdas filter)")
|
||||
void calculerTotaux_withNullAmounts() {
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece("X");
|
||||
e.setDateEcriture(LocalDate.now());
|
||||
e.setLibelle("L");
|
||||
// Ligne avec montants null → filtrée (couvre branch false: amount != null → false)
|
||||
LigneEcriture l1 = new LigneEcriture();
|
||||
l1.setMontantDebit(null);
|
||||
l1.setMontantCredit(null);
|
||||
LigneEcriture l2 = new LigneEcriture();
|
||||
l2.setMontantDebit(new BigDecimal("50"));
|
||||
l2.setMontantCredit(new BigDecimal("50"));
|
||||
e.getLignes().add(l1);
|
||||
e.getLignes().add(l2);
|
||||
e.calculerTotaux();
|
||||
assertThat(e.getMontantDebit()).isEqualByComparingTo("50");
|
||||
assertThat(e.getMontantCredit()).isEqualByComparingTo("50");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,622 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutWebhook;
|
||||
import dev.lions.unionflow.server.api.enums.communication.ConversationType;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests de couverture pour les branches manquées dans les entités.
|
||||
* Cible les branches des @PrePersist/@PreUpdate et des méthodes métier
|
||||
* non couvertes par les tests unitaires existants.
|
||||
*/
|
||||
@DisplayName("EntityCoverageTest — branches @PrePersist et méthodes métier")
|
||||
class EntityCoverageTest {
|
||||
|
||||
// ─── Membre ──────────────────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Membre — branches manquées")
|
||||
class MembreCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec statutCompte=null l'initialise à EN_ATTENTE_VALIDATION")
|
||||
void onCreate_nullStatutCompte_setsDefault() {
|
||||
// On ne peut pas appeler directement @PrePersist hors container JPA,
|
||||
// mais on peut tester la branche via reflection ou en vérifiant la valeur Builder.Default.
|
||||
// Alternative : vérifier que le Builder.Default couvre le null au niveau entité.
|
||||
Membre m = new Membre();
|
||||
m.setNumeroMembre("X");
|
||||
m.setPrenom("A");
|
||||
m.setNom("B");
|
||||
m.setEmail("a@test.com");
|
||||
m.setDateNaissance(LocalDate.now());
|
||||
// La valeur par défaut via Builder.Default est "EN_ATTENTE_VALIDATION"
|
||||
// mais l'instanciation via new Membre() ne déclenche pas le @Builder.Default.
|
||||
// On couvre la branche en appelant le @PrePersist directement via under-test:
|
||||
m.setStatutCompte(null);
|
||||
// Invoke the protected onCreate via direct call (simulated)
|
||||
m.onCreate();
|
||||
assertThat(m.getStatutCompte()).isEqualTo("EN_ATTENTE_VALIDATION");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAge() avec dateNaissance=null retourne 0")
|
||||
void getAge_nullDateNaissance_returnsZero() {
|
||||
Membre m = new Membre();
|
||||
m.setDateNaissance(null);
|
||||
assertThat(m.getAge()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isMajeur() avec dateNaissance=null retourne false")
|
||||
void isMajeur_nullDateNaissance_returnsFalse() {
|
||||
Membre m = new Membre();
|
||||
m.setDateNaissance(null);
|
||||
assertThat(m.isMajeur()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── MembreOrganisation ───────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("MembreOrganisation — branches manquées")
|
||||
class MembreOrganisationCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec statutMembre=null l'initialise à EN_ATTENTE_VALIDATION")
|
||||
void onCreate_nullStatutMembre_setsDefault() {
|
||||
MembreOrganisation mo = new MembreOrganisation();
|
||||
mo.setStatutMembre(null);
|
||||
mo.onCreate();
|
||||
assertThat(mo.getStatutMembre()).isEqualTo(StatutMembre.EN_ATTENTE_VALIDATION);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── ModuleOrganisationActif ──────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("ModuleOrganisationActif — isActif() non couvert")
|
||||
class ModuleOrganisationActifCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif() retourne true quand actif=true")
|
||||
void isActif_true() {
|
||||
ModuleOrganisationActif m = new ModuleOrganisationActif();
|
||||
m.setActif(true);
|
||||
m.setModuleCode("TEST");
|
||||
assertThat(m.isActif()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif() retourne false quand actif=false")
|
||||
void isActif_false() {
|
||||
ModuleOrganisationActif m = new ModuleOrganisationActif();
|
||||
m.setActif(false);
|
||||
m.setModuleCode("TEST");
|
||||
assertThat(m.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif() retourne false quand actif=null")
|
||||
void isActif_null() {
|
||||
ModuleOrganisationActif m = new ModuleOrganisationActif();
|
||||
m.setActif(null);
|
||||
assertThat(m.isActif()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── InscriptionEvenement — preUpdate ─────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("InscriptionEvenement — preUpdate() non couvert")
|
||||
class InscriptionEvenementCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("preUpdate() appelle super.onUpdate() sans exception")
|
||||
void preUpdate_doesNotThrow() {
|
||||
InscriptionEvenement ie = new InscriptionEvenement();
|
||||
ie.setStatut("CONFIRMEE");
|
||||
ie.preUpdate();
|
||||
// La date de modification est setDateModification — on vérifie juste que ça ne plante pas
|
||||
assertThat(ie.getDateModification()).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Conversation — onUpdate ─────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Conversation — onUpdate() non couvert")
|
||||
class ConversationCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("Conversation getters/setters de base")
|
||||
void gettersSetters() {
|
||||
Conversation c = new Conversation();
|
||||
c.setName("Chat Test");
|
||||
c.setDescription("Description");
|
||||
c.setType(ConversationType.GROUP);
|
||||
c.setIsMuted(false);
|
||||
c.setIsPinned(true);
|
||||
c.setIsArchived(false);
|
||||
c.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
assertThat(c.getName()).isEqualTo("Chat Test");
|
||||
assertThat(c.getType()).isEqualTo(ConversationType.GROUP);
|
||||
assertThat(c.getIsPinned()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onUpdate() met à jour updatedAt")
|
||||
void onUpdate_setsUpdatedAt() {
|
||||
Conversation c = new Conversation();
|
||||
c.setName("Chat");
|
||||
c.setType(ConversationType.INDIVIDUAL);
|
||||
assertThat(c.getUpdatedAt()).isNull();
|
||||
c.onUpdate();
|
||||
assertThat(c.getUpdatedAt()).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── EcritureComptable — onUpdate ────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("EcritureComptable — onUpdate() non couvert")
|
||||
class EcritureComptableCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onUpdate() appelle calculerTotaux")
|
||||
void onUpdate_calculerTotaux() {
|
||||
EcritureComptable e = new EcritureComptable();
|
||||
e.setNumeroPiece("ECR-UPD-001");
|
||||
e.setDateEcriture(java.time.LocalDate.now());
|
||||
e.setLibelle("Test update");
|
||||
e.setMontantDebit(java.math.BigDecimal.ZERO);
|
||||
e.setMontantCredit(java.math.BigDecimal.ZERO);
|
||||
// onUpdate calls calculerTotaux; with empty lignes, totals reset to ZERO
|
||||
e.onUpdate();
|
||||
assertThat(e.getMontantDebit()).isEqualByComparingTo(java.math.BigDecimal.ZERO);
|
||||
assertThat(e.getMontantCredit()).isEqualByComparingTo(java.math.BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── ValidationEtapeDemande — onCreate ───────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("ValidationEtapeDemande — onCreate() non couvert")
|
||||
class ValidationEtapeDemandeCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec statut=null initialise à EN_ATTENTE")
|
||||
void onCreate_nullStatut_setsDefault() {
|
||||
dev.lions.unionflow.server.api.enums.solidarite.StatutValidationEtape statut =
|
||||
dev.lions.unionflow.server.api.enums.solidarite.StatutValidationEtape.EN_ATTENTE;
|
||||
ValidationEtapeDemande ved = new ValidationEtapeDemande();
|
||||
ved.setStatut(null);
|
||||
ved.onCreate();
|
||||
assertThat(ved.getStatut()).isEqualTo(statut);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec statut déjà défini le préserve")
|
||||
void onCreate_existingStatut_preserves() {
|
||||
dev.lions.unionflow.server.api.enums.solidarite.StatutValidationEtape statut =
|
||||
dev.lions.unionflow.server.api.enums.solidarite.StatutValidationEtape.APPROUVEE;
|
||||
ValidationEtapeDemande ved = new ValidationEtapeDemande();
|
||||
ved.setStatut(statut);
|
||||
ved.onCreate();
|
||||
assertThat(ved.getStatut()).isEqualTo(statut);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── SystemAlert — branches @PrePersist ──────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("SystemAlert — branches @PrePersist non couvertes")
|
||||
class SystemAlertCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec timestamp=null l'initialise")
|
||||
void onCreate_nullTimestamp_setsNow() {
|
||||
SystemAlert a = new SystemAlert();
|
||||
a.setLevel("WARNING");
|
||||
a.setTitle("Test");
|
||||
a.setMessage("Msg");
|
||||
a.setTimestamp(null);
|
||||
a.setAcknowledged(null);
|
||||
a.onCreate();
|
||||
assertThat(a.getTimestamp()).isNotNull();
|
||||
assertThat(a.getAcknowledged()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec timestamp et acknowledged déjà définis ne les écrase pas")
|
||||
void onCreate_withExistingValues_preserves() {
|
||||
LocalDateTime ts = LocalDateTime.of(2026, 1, 1, 0, 0);
|
||||
SystemAlert a = new SystemAlert();
|
||||
a.setTimestamp(ts);
|
||||
a.setAcknowledged(true);
|
||||
a.onCreate();
|
||||
assertThat(a.getTimestamp()).isEqualTo(ts);
|
||||
assertThat(a.getAcknowledged()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── SystemLog — branche @PrePersist ─────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("SystemLog — branche @PrePersist non couverte")
|
||||
class SystemLogCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec timestamp=null l'initialise")
|
||||
void onCreate_nullTimestamp_setsNow() {
|
||||
SystemLog sl = new SystemLog();
|
||||
sl.setLevel("ERROR");
|
||||
sl.setSource("TEST");
|
||||
sl.setMessage("Message test");
|
||||
sl.setTimestamp(null);
|
||||
sl.onCreate();
|
||||
assertThat(sl.getTimestamp()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Getters/setters de SystemLog")
|
||||
void gettersSetters() {
|
||||
SystemLog sl = new SystemLog();
|
||||
sl.setLevel("INFO");
|
||||
sl.setSource("Database");
|
||||
sl.setMessage("DB query");
|
||||
sl.setDetails("stack trace here");
|
||||
sl.setTimestamp(LocalDateTime.now());
|
||||
sl.setUserId("user-123");
|
||||
sl.setSessionId("sess-abc");
|
||||
sl.setEndpoint("/api/test");
|
||||
sl.setHttpStatusCode(200);
|
||||
|
||||
assertThat(sl.getLevel()).isEqualTo("INFO");
|
||||
assertThat(sl.getSource()).isEqualTo("Database");
|
||||
assertThat(sl.getEndpoint()).isEqualTo("/api/test");
|
||||
assertThat(sl.getHttpStatusCode()).isEqualTo(200);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── WebhookWave — branches @PrePersist ──────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("WebhookWave — branches @PrePersist non couvertes")
|
||||
class WebhookWaveCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec statutTraitement=null l'initialise à EN_ATTENTE")
|
||||
void onCreate_nullStatut_setsDefault() {
|
||||
WebhookWave w = new WebhookWave();
|
||||
w.setWaveEventId("evt-" + UUID.randomUUID());
|
||||
w.setStatutTraitement(null);
|
||||
w.setNombreTentatives(null);
|
||||
w.setDateReception(null);
|
||||
w.onCreate();
|
||||
assertThat(w.getStatutTraitement()).isEqualTo(StatutWebhook.EN_ATTENTE.name());
|
||||
assertThat(w.getNombreTentatives()).isEqualTo(0);
|
||||
assertThat(w.getDateReception()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() ne surécrit pas des valeurs déjà définies")
|
||||
void onCreate_withExistingValues_preserves() {
|
||||
WebhookWave w = new WebhookWave();
|
||||
w.setWaveEventId("evt-" + UUID.randomUUID());
|
||||
w.setStatutTraitement(StatutWebhook.TRAITE.name());
|
||||
w.setNombreTentatives(3);
|
||||
LocalDateTime reception = LocalDateTime.of(2026, 1, 1, 12, 0);
|
||||
w.setDateReception(reception);
|
||||
w.onCreate();
|
||||
assertThat(w.getStatutTraitement()).isEqualTo(StatutWebhook.TRAITE.name());
|
||||
assertThat(w.getNombreTentatives()).isEqualTo(3);
|
||||
assertThat(w.getDateReception()).isEqualTo(reception);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Paiement — branches @PrePersist ─────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Paiement — branches @PrePersist non couvertes")
|
||||
class PaiementCoverage {
|
||||
|
||||
private Membre newMembre() {
|
||||
Membre m = new Membre();
|
||||
m.setId(UUID.randomUUID());
|
||||
m.setNumeroMembre("MX");
|
||||
m.setPrenom("X");
|
||||
m.setNom("Y");
|
||||
m.setEmail("xy@test.com");
|
||||
m.setDateNaissance(LocalDate.now());
|
||||
return m;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec numeroReference=null génère un numéro")
|
||||
void onCreate_nullNumeroReference_generates() {
|
||||
Paiement p = new Paiement();
|
||||
p.setNumeroReference(null);
|
||||
p.setStatutPaiement(null);
|
||||
p.setMontant(BigDecimal.ONE);
|
||||
p.setCodeDevise("XOF");
|
||||
p.setMethodePaiement("WAVE");
|
||||
p.setMembre(newMembre());
|
||||
p.onCreate();
|
||||
assertThat(p.getNumeroReference()).startsWith("PAY-");
|
||||
assertThat(p.getStatutPaiement()).isEqualTo("EN_ATTENTE");
|
||||
assertThat(p.getDatePaiement()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec numeroReference vide génère un numéro")
|
||||
void onCreate_emptyNumeroReference_generates() {
|
||||
Paiement p = new Paiement();
|
||||
p.setNumeroReference("");
|
||||
p.setStatutPaiement("VALIDE");
|
||||
p.setMontant(BigDecimal.ONE);
|
||||
p.setCodeDevise("XOF");
|
||||
p.setMethodePaiement("WAVE");
|
||||
p.setMembre(newMembre());
|
||||
p.setDatePaiement(LocalDateTime.now()); // already set
|
||||
p.onCreate();
|
||||
assertThat(p.getNumeroReference()).startsWith("PAY-");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreModifie() retourne false pour ANNULE")
|
||||
void peutEtreModifie_annule_returnsFalse() {
|
||||
Paiement p = new Paiement();
|
||||
p.setNumeroReference("X");
|
||||
p.setMontant(BigDecimal.ONE);
|
||||
p.setCodeDevise("XOF");
|
||||
p.setMethodePaiement("WAVE");
|
||||
p.setMembre(newMembre());
|
||||
p.setStatutPaiement("ANNULE");
|
||||
assertThat(p.peutEtreModifie()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── DemandeAide — branches @PrePersist ──────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("DemandeAide — branches @PrePersist non couvertes")
|
||||
class DemandeAideCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec dateDemande/statut/urgence=null initialise les defaults")
|
||||
void onCreate_nullFields_setsDefaults() {
|
||||
DemandeAide d = new DemandeAide();
|
||||
d.setDateDemande(null);
|
||||
d.setStatut(null);
|
||||
d.setUrgence(null);
|
||||
d.onCreate();
|
||||
assertThat(d.getDateDemande()).isNotNull();
|
||||
assertThat(d.getStatut()).isEqualTo(StatutAide.EN_ATTENTE);
|
||||
assertThat(d.getUrgence()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── LigneEcriture — branches @PrePersist + getMontant ───────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("LigneEcriture — branches non couvertes")
|
||||
class LigneEcritureCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec montantDebit/montantCredit=null les initialise à ZERO")
|
||||
void onCreate_nullMontants_setsZero() {
|
||||
LigneEcriture le = new LigneEcriture();
|
||||
le.setMontantDebit(null);
|
||||
le.setMontantCredit(null);
|
||||
le.onCreate();
|
||||
assertThat(le.getMontantDebit()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
assertThat(le.getMontantCredit()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getMontant() retourne ZERO quand les deux montants sont null/zero")
|
||||
void getMontant_noDebitNoCredit_returnsZero() {
|
||||
LigneEcriture le = new LigneEcriture();
|
||||
le.setMontantDebit(null);
|
||||
le.setMontantCredit(null);
|
||||
assertThat(le.getMontant()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── MembreRole — branches isActif ────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("MembreRole — branches isActif() et @PrePersist non couverts")
|
||||
class MembreRoleCoverage {
|
||||
|
||||
private MembreRole newMembreRole() {
|
||||
MembreRole mr = new MembreRole();
|
||||
mr.setActif(true);
|
||||
return mr;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif() retourne false si actif=false")
|
||||
void isActif_inactif_returnsFalse() {
|
||||
MembreRole mr = newMembreRole();
|
||||
mr.setActif(false);
|
||||
assertThat(mr.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif() retourne false si dateFin est dépassée")
|
||||
void isActif_dateFinPassed_returnsFalse() {
|
||||
MembreRole mr = newMembreRole();
|
||||
mr.setDateDebut(LocalDate.now().minusDays(10));
|
||||
mr.setDateFin(LocalDate.now().minusDays(1));
|
||||
assertThat(mr.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec dateDebut=null l'initialise à aujourd'hui")
|
||||
void onCreate_nullDateDebut_setsToday() {
|
||||
MembreRole mr = new MembreRole();
|
||||
mr.setDateDebut(null);
|
||||
mr.onCreate();
|
||||
assertThat(mr.getDateDebut()).isEqualTo(LocalDate.now());
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Permission — branche @PrePersist ────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Permission — branche @PrePersist non couverte")
|
||||
class PermissionCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec code=null et module/ressource/action définis génère le code")
|
||||
void onCreate_nullCode_withModuleRessourceAction_generatesCode() {
|
||||
Permission p = new Permission();
|
||||
p.setCode(null);
|
||||
p.setModule("FINANCE");
|
||||
p.setRessource("COTISATION");
|
||||
p.setAction("CREATE");
|
||||
p.onCreate();
|
||||
assertThat(p.getCode()).isEqualTo("FINANCE > COTISATION > CREATE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec code=null et module=null ne génère rien")
|
||||
void onCreate_nullCode_nullModule_noGeneration() {
|
||||
Permission p = new Permission();
|
||||
p.setCode(null);
|
||||
p.setModule(null);
|
||||
p.setRessource(null);
|
||||
p.setAction(null);
|
||||
p.onCreate();
|
||||
assertThat(p.getCode()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Role — branches @PrePersist ─────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Role — branches @PrePersist non couvertes")
|
||||
class RoleCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec typeRole/niveauHierarchique=null initialise les defaults")
|
||||
void onCreate_nullFields_setsDefaults() {
|
||||
Role r = new Role();
|
||||
r.setTypeRole(null);
|
||||
r.setNiveauHierarchique(null);
|
||||
r.onCreate();
|
||||
assertThat(r.getTypeRole()).isNotNull();
|
||||
assertThat(r.getNiveauHierarchique()).isEqualTo(100);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Document — branches @PrePersist ─────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Document — branches @PrePersist non couvertes")
|
||||
class DocumentCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec nombreTelechargements/typeDocument=null initialise les defaults")
|
||||
void onCreate_nullFields_setsDefaults() {
|
||||
Document d = new Document();
|
||||
d.setNombreTelechargements(null);
|
||||
d.setTypeDocument(null);
|
||||
d.onCreate();
|
||||
assertThat(d.getNombreTelechargements()).isEqualTo(0);
|
||||
assertThat(d.getTypeDocument()).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── PieceJointe — branche @PrePersist ───────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("PieceJointe — branche @PrePersist non couverte")
|
||||
class PieceJointeCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec ordre=null l'initialise à 1")
|
||||
void onCreate_nullOrdre_setsOne() {
|
||||
PieceJointe pj = new PieceJointe();
|
||||
pj.setOrdre(null);
|
||||
pj.onCreate();
|
||||
assertThat(pj.getOrdre()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Adresse — branche @PrePersist ───────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Adresse — branche @PrePersist non couverte")
|
||||
class AdresseCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec principale=null l'initialise à false")
|
||||
void onCreate_nullPrincipale_setsFalse() {
|
||||
Adresse a = new Adresse();
|
||||
a.setPrincipale(null);
|
||||
a.onCreate();
|
||||
assertThat(a.getPrincipale()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── JournalComptable — branche @PrePersist ──────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("JournalComptable — branche @PrePersist non couverte")
|
||||
class JournalComptableCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec statut=null initialise à OUVERT")
|
||||
void onCreate_nullStatut_setsOuvert() {
|
||||
JournalComptable jc = new JournalComptable();
|
||||
jc.setStatut(null);
|
||||
jc.onCreate();
|
||||
assertThat(jc.getStatut()).isEqualTo("OUVERT");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec statut vide initialise à OUVERT")
|
||||
void onCreate_emptyStatut_setsOuvert() {
|
||||
JournalComptable jc = new JournalComptable();
|
||||
jc.setStatut("");
|
||||
jc.onCreate();
|
||||
assertThat(jc.getStatut()).isEqualTo("OUVERT");
|
||||
}
|
||||
}
|
||||
|
||||
// ─── SuggestionVote — branches @PrePersist ───────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("SuggestionVote — branches @PrePersist non couvertes")
|
||||
class SuggestionVoteCoverage {
|
||||
|
||||
@Test
|
||||
@DisplayName("onPrePersist() avec dateVote=null l'initialise")
|
||||
void onPrePersist_nullDateVote_setsNow() {
|
||||
SuggestionVote sv = new SuggestionVote();
|
||||
sv.setSuggestionId(UUID.randomUUID());
|
||||
sv.setUtilisateurId(UUID.randomUUID());
|
||||
sv.setDateVote(null);
|
||||
sv.setDateCreation(null);
|
||||
sv.onPrePersist();
|
||||
assertThat(sv.getDateVote()).isNotNull();
|
||||
assertThat(sv.getDateCreation()).isNotNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests de couverture complémentaires pour Evenement.
|
||||
* Couvre les branches manquantes de isTermine(), isEnCours(), getTauxRemplissage().
|
||||
*/
|
||||
@DisplayName("Evenement - couverture complémentaire")
|
||||
class EvenementCoverageTest {
|
||||
|
||||
private Evenement buildEvenement() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Test Evenement");
|
||||
e.setDateDebut(LocalDateTime.now().plusDays(1));
|
||||
e.setStatut("PLANIFIE");
|
||||
return e;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isTermine: true si dateFin est dans le passé (statut non TERMINE)")
|
||||
void isTermine_pastDateFin_returnsTrue() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setDateDebut(LocalDateTime.now().minusDays(2));
|
||||
e.setDateFin(LocalDateTime.now().minusHours(1)); // dateFin passée
|
||||
e.setStatut("CONFIRME"); // pas TERMINE
|
||||
assertThat(e.isTermine()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isTermine: false si dateFin est dans le futur (statut non TERMINE)")
|
||||
void isTermine_futureDateFin_returnsFalse() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setDateFin(LocalDateTime.now().plusHours(2)); // dateFin future
|
||||
e.setStatut("PLANIFIE");
|
||||
assertThat(e.isTermine()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isEnCours: false si dateDebut future")
|
||||
void isEnCours_futureDateDebut_returnsFalse() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setDateDebut(LocalDateTime.now().plusHours(1));
|
||||
assertThat(e.isEnCours()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isEnCours: true si dateDebut passée et dateFin null (événement sans fin définie)")
|
||||
void isEnCours_noDateFin_returnsTrue() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setDateDebut(LocalDateTime.now().minusHours(1));
|
||||
e.setDateFin(null); // pas de dateFin → toujours en cours si après dateDebut
|
||||
assertThat(e.isEnCours()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isEnCours: false si dateFin passée")
|
||||
void isEnCours_pastDateFin_returnsFalse() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setDateDebut(LocalDateTime.now().minusHours(2));
|
||||
e.setDateFin(LocalDateTime.now().minusHours(1)); // dateFin passée
|
||||
assertThat(e.isEnCours()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getTauxRemplissage: null si capaciteMax est 0")
|
||||
void getTauxRemplissage_zeroCapaciteMax_returnsNull() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setCapaciteMax(0);
|
||||
assertThat(e.getTauxRemplissage()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getTauxRemplissage: retourne le pourcentage si inscriptions")
|
||||
void getTauxRemplissage_withInscriptions_returnsPercentage() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setCapaciteMax(4);
|
||||
|
||||
Membre m1 = new Membre();
|
||||
m1.setId(java.util.UUID.randomUUID());
|
||||
InscriptionEvenement i1 = new InscriptionEvenement();
|
||||
i1.setMembre(m1);
|
||||
i1.setStatut(InscriptionEvenement.StatutInscription.CONFIRMEE.name());
|
||||
e.getInscriptions().add(i1);
|
||||
|
||||
Membre m2 = new Membre();
|
||||
m2.setId(java.util.UUID.randomUUID());
|
||||
InscriptionEvenement i2 = new InscriptionEvenement();
|
||||
i2.setMembre(m2);
|
||||
i2.setStatut(InscriptionEvenement.StatutInscription.CONFIRMEE.name());
|
||||
e.getInscriptions().add(i2);
|
||||
|
||||
// 2 inscrits sur 4 = 50%
|
||||
assertThat(e.getTauxRemplissage()).isEqualTo(50.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getPlacesRestantes: 0 si nbInscrits >= capaciteMax")
|
||||
void getPlacesRestantes_full_returnsZero() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setCapaciteMax(1);
|
||||
|
||||
Membre m = new Membre();
|
||||
m.setId(java.util.UUID.randomUUID());
|
||||
InscriptionEvenement i = new InscriptionEvenement();
|
||||
i.setMembre(m);
|
||||
i.setStatut(InscriptionEvenement.StatutInscription.CONFIRMEE.name());
|
||||
e.getInscriptions().add(i);
|
||||
|
||||
// capaciteMax=1, inscrits=1 → placesRestantes=0
|
||||
assertThat(e.getPlacesRestantes()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isMemberInscrit: false si membre non inscrit mais d'autres sont inscrits")
|
||||
void isMemberInscrit_differentMembre_returnsFalse() {
|
||||
Evenement e = buildEvenement();
|
||||
|
||||
Membre m = new Membre();
|
||||
m.setId(java.util.UUID.randomUUID());
|
||||
InscriptionEvenement i = new InscriptionEvenement();
|
||||
i.setMembre(m);
|
||||
i.setStatut(InscriptionEvenement.StatutInscription.CONFIRMEE.name());
|
||||
e.getInscriptions().add(i);
|
||||
|
||||
// Vérifie un autre membre (non inscrit)
|
||||
assertThat(e.isMemberInscrit(java.util.UUID.randomUUID())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isMemberInscrit: false si inscription non CONFIRMEE")
|
||||
void isMemberInscrit_nonConfirmee_returnsFalse() {
|
||||
Evenement e = buildEvenement();
|
||||
java.util.UUID membreId = java.util.UUID.randomUUID();
|
||||
|
||||
Membre m = new Membre();
|
||||
m.setId(membreId);
|
||||
InscriptionEvenement i = new InscriptionEvenement();
|
||||
i.setMembre(m);
|
||||
i.setStatut(InscriptionEvenement.StatutInscription.ANNULEE.name()); // pas CONFIRMEE
|
||||
e.getInscriptions().add(i);
|
||||
|
||||
assertThat(e.isMemberInscrit(membreId)).isFalse();
|
||||
}
|
||||
|
||||
// ── Branches manquantes ───────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Branch manquante dans isComplet() : capaciteMax == null → false.
|
||||
* Le code : `return capaciteMax != null && getNombreInscrits() >= capaciteMax`
|
||||
* → quand capaciteMax est null, la condition court-circuite (false).
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("isComplet: false si capaciteMax null (capacité illimitée)")
|
||||
void isComplet_capaciteMaxNull_returnsFalse() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setCapaciteMax(null); // illimité → jamais complet
|
||||
|
||||
assertThat(e.isComplet()).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch manquante dans getTauxRemplissage() : capaciteMax == null → null.
|
||||
* Le code : `if (capaciteMax == null || capaciteMax == 0) { return null; }`
|
||||
* → première condition (capaciteMax == null) doit retourner null.
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("getTauxRemplissage: null si capaciteMax est null")
|
||||
void getTauxRemplissage_capaciteMaxNull_returnsNull() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setCapaciteMax(null);
|
||||
|
||||
assertThat(e.getTauxRemplissage()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch manquante dans isMemberInscrit() : inscriptions == null → false.
|
||||
* Le code : `return inscriptions != null && inscriptions.stream()...`
|
||||
* → quand inscriptions est null, retourne false.
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("isMemberInscrit: false si inscriptions est null")
|
||||
void isMemberInscrit_inscriptionsNull_returnsFalse() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setInscriptions(null);
|
||||
|
||||
assertThat(e.isMemberInscrit(java.util.UUID.randomUUID())).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch manquante dans isOuvertAuxInscriptions() : quand capaciteMax != null
|
||||
* mais getNombreInscrits() < capaciteMax → ne retourne pas false (capacité non atteinte),
|
||||
* passe à la vérification du statut.
|
||||
* Couvre le chemin où la branche capacité est false (non bloquante).
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("isOuvertAuxInscriptions: true si capaciteMax définie mais non atteinte")
|
||||
void isOuvertAuxInscriptions_capaciteNonAtteinte_returnsTrue() {
|
||||
Evenement e = buildEvenement();
|
||||
e.setInscriptionRequise(true);
|
||||
e.setActif(true);
|
||||
e.setCapaciteMax(5); // capacité non atteinte (0 inscrits)
|
||||
|
||||
assertThat(e.isOuvertAuxInscriptions()).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -240,4 +240,138 @@ class EvenementTest {
|
||||
e.setDateDebut(LocalDateTime.now());
|
||||
assertThat(e.isMemberInscrit(UUID.randomUUID())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isOuvertAuxInscriptions: false si actif=false")
|
||||
void isOuvertAuxInscriptions_false_actifFalse() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now().plusDays(1));
|
||||
e.setInscriptionRequise(true);
|
||||
e.setStatut("PLANIFIE");
|
||||
e.setActif(false);
|
||||
assertThat(e.isOuvertAuxInscriptions()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isOuvertAuxInscriptions: false si date limite dépassée")
|
||||
void isOuvertAuxInscriptions_false_dateLimitePassed() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now().plusDays(1));
|
||||
e.setDateLimiteInscription(LocalDateTime.now().minusDays(1));
|
||||
e.setInscriptionRequise(true);
|
||||
e.setStatut("PLANIFIE");
|
||||
e.setActif(true);
|
||||
assertThat(e.isOuvertAuxInscriptions()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isOuvertAuxInscriptions: false si événement déjà commencé")
|
||||
void isOuvertAuxInscriptions_false_dateDebutPassed() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now().minusHours(1));
|
||||
e.setInscriptionRequise(true);
|
||||
e.setStatut("PLANIFIE");
|
||||
e.setActif(true);
|
||||
assertThat(e.isOuvertAuxInscriptions()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isOuvertAuxInscriptions: false si capacité atteinte")
|
||||
void isOuvertAuxInscriptions_false_capacitePleine() {
|
||||
Membre m = new Membre();
|
||||
m.setId(UUID.randomUUID());
|
||||
InscriptionEvenement i = new InscriptionEvenement();
|
||||
i.setMembre(m);
|
||||
i.setStatut(InscriptionEvenement.StatutInscription.CONFIRMEE.name());
|
||||
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now().plusDays(1));
|
||||
e.setInscriptionRequise(true);
|
||||
e.setStatut("PLANIFIE");
|
||||
e.setActif(true);
|
||||
e.setCapaciteMax(1);
|
||||
e.getInscriptions().add(i);
|
||||
assertThat(e.isOuvertAuxInscriptions()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isOuvertAuxInscriptions: true si statut CONFIRME")
|
||||
void isOuvertAuxInscriptions_true_statutConfirme() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now().plusDays(1));
|
||||
e.setInscriptionRequise(true);
|
||||
e.setStatut("CONFIRME");
|
||||
e.setActif(true);
|
||||
assertThat(e.isOuvertAuxInscriptions()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getNombreInscrits: 0 si inscriptions null")
|
||||
void getNombreInscrits_inscriptionsNull_returnsZero() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now());
|
||||
e.setInscriptions(null);
|
||||
assertThat(e.getNombreInscrits()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isTermine: true si dateFin passée et statut non TERMINE")
|
||||
void isTermine_true_datefinPassed() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now().minusDays(3));
|
||||
e.setDateFin(LocalDateTime.now().minusDays(1));
|
||||
e.setStatut("PLANIFIE");
|
||||
assertThat(e.isTermine()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isOuvertAuxInscriptions: false si statut ni PLANIFIE ni CONFIRME")
|
||||
void isOuvertAuxInscriptions_false_statutAutre() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now().plusDays(1));
|
||||
e.setInscriptionRequise(true);
|
||||
e.setStatut("EN_COURS");
|
||||
e.setActif(true);
|
||||
assertThat(e.isOuvertAuxInscriptions()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isMemberInscrit: false si membre non confirmé")
|
||||
void isMemberInscrit_false_notConfirmed() {
|
||||
UUID membreId = UUID.randomUUID();
|
||||
Membre m = new Membre();
|
||||
m.setId(membreId);
|
||||
InscriptionEvenement i = new InscriptionEvenement();
|
||||
i.setMembre(m);
|
||||
i.setStatut(InscriptionEvenement.StatutInscription.EN_ATTENTE.name());
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now());
|
||||
e.getInscriptions().add(i);
|
||||
assertThat(e.isMemberInscrit(membreId)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isMemberInscrit: false si mauvais id membre")
|
||||
void isMemberInscrit_false_wrongId() {
|
||||
UUID membreId = UUID.randomUUID();
|
||||
Membre m = new Membre();
|
||||
m.setId(UUID.randomUUID()); // different id
|
||||
InscriptionEvenement i = new InscriptionEvenement();
|
||||
i.setMembre(m);
|
||||
i.setStatut(InscriptionEvenement.StatutInscription.CONFIRMEE.name());
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Ev");
|
||||
e.setDateDebut(LocalDateTime.now());
|
||||
e.getInscriptions().add(i);
|
||||
assertThat(e.isMemberInscrit(membreId)).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour l'entité FeedbackEvenement.
|
||||
* Tests purs (sans Quarkus) car les méthodes sont de la logique en mémoire.
|
||||
*/
|
||||
@DisplayName("FeedbackEvenement")
|
||||
class FeedbackEvenementTest {
|
||||
|
||||
private FeedbackEvenement buildFeedback() {
|
||||
FeedbackEvenement fb = new FeedbackEvenement();
|
||||
fb.setNote(4);
|
||||
fb.setCommentaire("Très bon événement");
|
||||
fb.setDateFeedback(LocalDateTime.now());
|
||||
fb.setModerationStatut(FeedbackEvenement.ModerationStatut.PUBLIE.name());
|
||||
return fb;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPublie retourne true quand statut est PUBLIE")
|
||||
void isPublie_quandStatutPublie_retourneTrue() {
|
||||
FeedbackEvenement fb = buildFeedback();
|
||||
fb.setModerationStatut(FeedbackEvenement.ModerationStatut.PUBLIE.name());
|
||||
|
||||
assertThat(fb.isPublie()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPublie retourne false quand statut est EN_ATTENTE")
|
||||
void isPublie_quandStatutEnAttente_retourneFalse() {
|
||||
FeedbackEvenement fb = buildFeedback();
|
||||
fb.setModerationStatut(FeedbackEvenement.ModerationStatut.EN_ATTENTE.name());
|
||||
|
||||
assertThat(fb.isPublie()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPublie retourne false quand statut est REJETE")
|
||||
void isPublie_quandStatutRejete_retourneFalse() {
|
||||
FeedbackEvenement fb = buildFeedback();
|
||||
fb.setModerationStatut(FeedbackEvenement.ModerationStatut.REJETE.name());
|
||||
|
||||
assertThat(fb.isPublie()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("mettreEnAttente modifie le statut et la raison")
|
||||
void mettreEnAttente_modifieStatutEtRaison() {
|
||||
FeedbackEvenement fb = buildFeedback();
|
||||
|
||||
fb.mettreEnAttente("Contenu inapproprié");
|
||||
|
||||
assertThat(fb.getModerationStatut()).isEqualTo(FeedbackEvenement.ModerationStatut.EN_ATTENTE.name());
|
||||
assertThat(fb.getRaisonModeration()).isEqualTo("Contenu inapproprié");
|
||||
assertThat(fb.isPublie()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publier restaure le statut PUBLIE et efface la raison")
|
||||
void publier_restaureStatutPublieEtEffaceRaison() {
|
||||
FeedbackEvenement fb = buildFeedback();
|
||||
fb.mettreEnAttente("Raison de test");
|
||||
|
||||
fb.publier();
|
||||
|
||||
assertThat(fb.getModerationStatut()).isEqualTo(FeedbackEvenement.ModerationStatut.PUBLIE.name());
|
||||
assertThat(fb.getRaisonModeration()).isNull();
|
||||
assertThat(fb.isPublie()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("rejeter modifie le statut à REJETE et stocke la raison")
|
||||
void rejeter_modifieStatutEtRaison() {
|
||||
FeedbackEvenement fb = buildFeedback();
|
||||
|
||||
fb.rejeter("Commentaire offensant");
|
||||
|
||||
assertThat(fb.getModerationStatut()).isEqualTo(FeedbackEvenement.ModerationStatut.REJETE.name());
|
||||
assertThat(fb.getRaisonModeration()).isEqualTo("Commentaire offensant");
|
||||
assertThat(fb.isPublie()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toString retourne une représentation textuelle avec membre et evenement null")
|
||||
void toString_avecMembreEtEvenementNull_retourneChaineSansException() {
|
||||
FeedbackEvenement fb = buildFeedback();
|
||||
fb.setMembre(null);
|
||||
fb.setEvenement(null);
|
||||
fb.setNote(3);
|
||||
|
||||
String result = fb.toString();
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).contains("FeedbackEvenement");
|
||||
assertThat(result).contains("null"); // membre et evenement sont null
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toString retourne une représentation textuelle avec membre et evenement renseignés")
|
||||
void toString_avecMembreEtEvenement_retourneRepresentationComplete() {
|
||||
FeedbackEvenement fb = buildFeedback();
|
||||
Membre membre = new Membre();
|
||||
membre.setEmail("test@test.com");
|
||||
fb.setMembre(membre);
|
||||
|
||||
Evenement evenement = new Evenement();
|
||||
evenement.setTitre("AG 2026");
|
||||
evenement.setDateDebut(LocalDateTime.now());
|
||||
fb.setEvenement(evenement);
|
||||
fb.setNote(5);
|
||||
|
||||
String result = fb.toString();
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).contains("FeedbackEvenement");
|
||||
assertThat(result).contains("test@test.com");
|
||||
assertThat(result).contains("AG 2026");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("preUpdate appelle onUpdate sans exception")
|
||||
void preUpdate_sansException() {
|
||||
FeedbackEvenement fb = buildFeedback();
|
||||
// dateModification initialement null (pas d'id/version puisque pas en base)
|
||||
// preUpdate() appelle super.onUpdate() qui met dateModification à now()
|
||||
// On vérifie que l'appel ne lance pas d'exception
|
||||
fb.preUpdate();
|
||||
assertThat(fb.getDateModification()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ModerationStatut enum contient les trois valeurs attendues")
|
||||
void moderationStatutEnum_contientValeurs() {
|
||||
FeedbackEvenement.ModerationStatut[] values = FeedbackEvenement.ModerationStatut.values();
|
||||
assertThat(values).hasSize(3);
|
||||
assertThat(values).contains(
|
||||
FeedbackEvenement.ModerationStatut.PUBLIE,
|
||||
FeedbackEvenement.ModerationStatut.EN_ATTENTE,
|
||||
FeedbackEvenement.ModerationStatut.REJETE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getters et setters fonctionnent correctement")
|
||||
void gettersSetters_fonctionnentCorrectement() {
|
||||
FeedbackEvenement fb = new FeedbackEvenement();
|
||||
LocalDateTime date = LocalDateTime.of(2026, 1, 15, 10, 0);
|
||||
|
||||
fb.setNote(3);
|
||||
fb.setCommentaire("Bien mais peut mieux faire");
|
||||
fb.setDateFeedback(date);
|
||||
fb.setModerationStatut(FeedbackEvenement.ModerationStatut.EN_ATTENTE.name());
|
||||
fb.setRaisonModeration("Vérification en cours");
|
||||
|
||||
assertThat(fb.getNote()).isEqualTo(3);
|
||||
assertThat(fb.getCommentaire()).isEqualTo("Bien mais peut mieux faire");
|
||||
assertThat(fb.getDateFeedback()).isEqualTo(date);
|
||||
assertThat(fb.getModerationStatut()).isEqualTo("EN_ATTENTE");
|
||||
assertThat(fb.getRaisonModeration()).isEqualTo("Vérification en cours");
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@ package dev.lions.unionflow.server.entity;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
@DisplayName("InscriptionEvenement")
|
||||
class InscriptionEvenementTest {
|
||||
@@ -118,4 +120,27 @@ class InscriptionEvenementTest {
|
||||
i.setEvenement(newEvenement());
|
||||
assertThat(i.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("preUpdate ne lève pas d'exception")
|
||||
void preUpdate_doesNotThrow() throws Exception {
|
||||
InscriptionEvenement i = new InscriptionEvenement();
|
||||
i.setMembre(newMembre());
|
||||
i.setEvenement(newEvenement());
|
||||
i.setStatut(InscriptionEvenement.StatutInscription.CONFIRMEE.name());
|
||||
Method preUpdate = InscriptionEvenement.class.getDeclaredMethod("preUpdate");
|
||||
preUpdate.setAccessible(true);
|
||||
assertThatCode(() -> preUpdate.invoke(i)).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toString avec membre et evenement null")
|
||||
void toString_nullMemberAndEvenement() {
|
||||
InscriptionEvenement i = new InscriptionEvenement();
|
||||
i.setMembre(null);
|
||||
i.setEvenement(null);
|
||||
i.setStatut("CONFIRMEE");
|
||||
String s = i.toString();
|
||||
assertThat(s).isNotNull().contains("null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.TypeObjetIntentionPaiement;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests pour {@link IntentionPaiement} — méthode onCreate().
|
||||
*/
|
||||
@DisplayName("IntentionPaiement — onCreate")
|
||||
class IntentionPaiementBranchTest {
|
||||
|
||||
// ── onCreate() ────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: dateExpiration déjà définie → conservée")
|
||||
void onCreate_dateExpirationDejaDefinie_conservee() throws Exception {
|
||||
IntentionPaiement ip = new IntentionPaiement();
|
||||
ip.setMontantTotal(new BigDecimal("5000.00"));
|
||||
ip.setTypeObjet(TypeObjetIntentionPaiement.COTISATION);
|
||||
|
||||
LocalDateTime expirationFixe = LocalDateTime.of(2025, 12, 31, 23, 59);
|
||||
ip.setDateExpiration(expirationFixe);
|
||||
|
||||
Method onCreate = IntentionPaiement.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(ip);
|
||||
|
||||
// dateExpiration doit rester la valeur fixée, pas être remplacée
|
||||
assertThat(ip.getDateExpiration()).isEqualTo(expirationFixe);
|
||||
// les autres defaults sont bien positionnés
|
||||
assertThat(ip.getStatut()).isEqualTo(StatutIntentionPaiement.INITIEE);
|
||||
assertThat(ip.getCodeDevise()).isEqualTo("XOF");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: dateExpiration null → positionnée à now+30min")
|
||||
void onCreate_dateExpirationNull_setToNowPlusTrente() throws Exception {
|
||||
IntentionPaiement ip = new IntentionPaiement();
|
||||
ip.setMontantTotal(new BigDecimal("5000.00"));
|
||||
ip.setTypeObjet(TypeObjetIntentionPaiement.COTISATION);
|
||||
ip.setStatut(null);
|
||||
ip.setCodeDevise(null);
|
||||
ip.setDateExpiration(null);
|
||||
|
||||
LocalDateTime avant = LocalDateTime.now();
|
||||
|
||||
Method onCreate = IntentionPaiement.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(ip);
|
||||
|
||||
LocalDateTime apres = LocalDateTime.now();
|
||||
|
||||
assertThat(ip.getDateExpiration()).isNotNull();
|
||||
assertThat(ip.getDateExpiration()).isAfterOrEqualTo(avant.plusMinutes(29));
|
||||
assertThat(ip.getDateExpiration()).isBeforeOrEqualTo(apres.plusMinutes(31));
|
||||
assertThat(ip.getStatut()).isEqualTo(StatutIntentionPaiement.INITIEE);
|
||||
assertThat(ip.getCodeDevise()).isEqualTo("XOF");
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import dev.lions.unionflow.server.api.enums.paiement.TypeObjetIntentionPaiement;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
@@ -121,4 +122,35 @@ class IntentionPaiementTest {
|
||||
i.setTypeObjet(TypeObjetIntentionPaiement.COTISATION);
|
||||
assertThat(i.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isExpiree: false si dateExpiration null")
|
||||
void isExpiree_nullDate_returnsFalse() {
|
||||
IntentionPaiement i = new IntentionPaiement();
|
||||
i.setUtilisateur(newMembre());
|
||||
i.setMontantTotal(BigDecimal.ONE);
|
||||
i.setTypeObjet(TypeObjetIntentionPaiement.COTISATION);
|
||||
i.setDateExpiration(null);
|
||||
assertThat(i.isExpiree()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise les défauts si null")
|
||||
void onCreate_setsDefaults() throws Exception {
|
||||
IntentionPaiement i = new IntentionPaiement();
|
||||
i.setUtilisateur(newMembre());
|
||||
i.setMontantTotal(BigDecimal.ONE);
|
||||
i.setTypeObjet(TypeObjetIntentionPaiement.COTISATION);
|
||||
i.setStatut(null);
|
||||
i.setCodeDevise(null);
|
||||
i.setDateExpiration(null);
|
||||
|
||||
Method onCreate = IntentionPaiement.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(i);
|
||||
|
||||
assertThat(i.getStatut()).isEqualTo(StatutIntentionPaiement.INITIEE);
|
||||
assertThat(i.getCodeDevise()).isEqualTo("XOF");
|
||||
assertThat(i.getDateExpiration()).isNotNull().isAfter(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.comptabilite.TypeJournalComptable;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests pour {@link JournalComptable} — méthodes estDansPeriode() et onCreate().
|
||||
*/
|
||||
@DisplayName("JournalComptable — estDansPeriode et onCreate")
|
||||
class JournalComptableBranchTest {
|
||||
|
||||
private JournalComptable newJournal() {
|
||||
JournalComptable j = new JournalComptable();
|
||||
j.setCode("AC");
|
||||
j.setLibelle("Achat");
|
||||
j.setTypeJournal(TypeJournalComptable.ACHATS);
|
||||
return j;
|
||||
}
|
||||
|
||||
// ── estDansPeriode() ──────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("estDansPeriode: dateDebut non null mais dateFin null → true (période illimitée)")
|
||||
void estDansPeriode_dateFinNull_returnsTrue() {
|
||||
JournalComptable j = newJournal();
|
||||
j.setDateDebut(LocalDate.of(2025, 1, 1));
|
||||
j.setDateFin(null);
|
||||
|
||||
assertThat(j.estDansPeriode(LocalDate.of(2025, 6, 15))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estDansPeriode: dateDebut null mais dateFin non null → true (période illimitée)")
|
||||
void estDansPeriode_dateDebutNull_returnsTrue() {
|
||||
JournalComptable j = newJournal();
|
||||
j.setDateDebut(null);
|
||||
j.setDateFin(LocalDate.of(2025, 12, 31));
|
||||
|
||||
assertThat(j.estDansPeriode(LocalDate.of(2025, 6, 15))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estDansPeriode: date dans la période → true")
|
||||
void estDansPeriode_dateInPeriode_returnsTrue() {
|
||||
JournalComptable j = newJournal();
|
||||
j.setDateDebut(LocalDate.of(2025, 1, 1));
|
||||
j.setDateFin(LocalDate.of(2025, 12, 31));
|
||||
|
||||
assertThat(j.estDansPeriode(LocalDate.of(2025, 6, 15))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estDansPeriode: date hors période → false")
|
||||
void estDansPeriode_dateOutsidePeriode_returnsFalse() {
|
||||
JournalComptable j = newJournal();
|
||||
j.setDateDebut(LocalDate.of(2025, 1, 1));
|
||||
j.setDateFin(LocalDate.of(2025, 6, 30));
|
||||
|
||||
assertThat(j.estDansPeriode(LocalDate.of(2025, 7, 1))).isFalse();
|
||||
}
|
||||
|
||||
// ── onCreate() ────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: statut null → OUVERT")
|
||||
void onCreate_statutNull_setsOuvert() throws Exception {
|
||||
JournalComptable j = newJournal();
|
||||
j.setStatut(null);
|
||||
|
||||
Method onCreate = JournalComptable.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(j);
|
||||
|
||||
assertThat(j.getStatut()).isEqualTo("OUVERT");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: statut vide → OUVERT")
|
||||
void onCreate_statutEmpty_setsOuvert() throws Exception {
|
||||
JournalComptable j = newJournal();
|
||||
j.setStatut("");
|
||||
|
||||
Method onCreate = JournalComptable.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(j);
|
||||
|
||||
assertThat(j.getStatut()).isEqualTo("OUVERT");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: statut déjà défini → conservé")
|
||||
void onCreate_statutDejaDefini_conserve() throws Exception {
|
||||
JournalComptable j = newJournal();
|
||||
j.setStatut("FERME");
|
||||
|
||||
Method onCreate = JournalComptable.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(j);
|
||||
|
||||
assertThat(j.getStatut()).isEqualTo("FERME");
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,30 @@ class LigneEcritureTest {
|
||||
assertThat(l.isValide()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isValide: false si ni débit ni crédit (both null)")
|
||||
void isValide_false_bothNull() {
|
||||
LigneEcriture l = new LigneEcriture();
|
||||
l.setEcriture(newEcriture());
|
||||
l.setCompteComptable(newCompte());
|
||||
l.setNumeroLigne(1);
|
||||
l.setMontantDebit(null);
|
||||
l.setMontantCredit(null);
|
||||
assertThat(l.isValide()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isValide: false si ni débit ni crédit (both zero)")
|
||||
void isValide_false_bothZero() {
|
||||
LigneEcriture l = new LigneEcriture();
|
||||
l.setEcriture(newEcriture());
|
||||
l.setCompteComptable(newCompte());
|
||||
l.setNumeroLigne(1);
|
||||
l.setMontantDebit(BigDecimal.ZERO);
|
||||
l.setMontantCredit(BigDecimal.ZERO);
|
||||
assertThat(l.isValide()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getMontant: débit ou crédit")
|
||||
void getMontant() {
|
||||
@@ -88,6 +112,18 @@ class LigneEcritureTest {
|
||||
assertThat(l.getMontant()).isEqualByComparingTo("200");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getMontant: retourne ZERO si ni débit ni crédit (both null)")
|
||||
void getMontant_zero_whenBothNull() {
|
||||
LigneEcriture l = new LigneEcriture();
|
||||
l.setEcriture(newEcriture());
|
||||
l.setCompteComptable(newCompte());
|
||||
l.setNumeroLigne(1);
|
||||
l.setMontantDebit(null);
|
||||
l.setMontantCredit(null);
|
||||
assertThat(l.getMontant()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("equals et hashCode")
|
||||
void equalsHashCode() {
|
||||
@@ -117,4 +153,26 @@ class LigneEcritureTest {
|
||||
l.setCompteComptable(newCompte());
|
||||
assertThat(l.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
// ── Branch coverage manquantes ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* L82 branch manquante : montantCredit != null mais == 0 (not > 0)
|
||||
* → `montantCredit != null && montantCredit.compareTo(BigDecimal.ZERO) > 0` → false
|
||||
* → retourne BigDecimal.ZERO (la dernière ligne)
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("getMontant: montantDebit null, montantCredit = ZERO → retourne ZERO (branche false compareTo)")
|
||||
void getMontant_debitNull_creditZero_returnsZero() {
|
||||
LigneEcriture l = new LigneEcriture();
|
||||
l.setEcriture(newEcriture());
|
||||
l.setCompteComptable(newCompte());
|
||||
l.setNumeroLigne(1);
|
||||
// montantDebit == null → premiere condition false
|
||||
// montantCredit != null mais == 0 → deuxième condition: compareTo > 0 est FALSE
|
||||
// → retourne BigDecimal.ZERO
|
||||
l.setMontantDebit(null);
|
||||
l.setMontantCredit(BigDecimal.ZERO);
|
||||
assertThat(l.getMontant()).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,18 @@ class MembreOrganisationTest {
|
||||
assertThat(mo.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif: false si statutMembre ACTIF mais actif=false (branche &&)")
|
||||
void isActif_statutActif_actifFalse_returnsFalse() {
|
||||
// statutMembre ACTIF mais actif=false → isActif() retourne false
|
||||
MembreOrganisation mo = new MembreOrganisation();
|
||||
mo.setMembre(newMembre());
|
||||
mo.setOrganisation(newOrganisation());
|
||||
mo.setStatutMembre(StatutMembre.ACTIF);
|
||||
mo.setActif(false);
|
||||
assertThat(mo.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutDemanderAide")
|
||||
void peutDemanderAide() {
|
||||
|
||||
@@ -60,6 +60,29 @@ class MembreRoleTest {
|
||||
assertThat(mr.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif: false si actif=false")
|
||||
void isActif_false_whenActifFalse() {
|
||||
MembreRole mr = new MembreRole();
|
||||
mr.setMembreOrganisation(newMembreOrganisation());
|
||||
mr.setRole(newRole());
|
||||
mr.setActif(false);
|
||||
mr.setDateDebut(LocalDate.now().minusDays(1));
|
||||
assertThat(mr.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif: false si dateFin dans le passé")
|
||||
void isActif_false_dateFinExpiree() {
|
||||
MembreRole mr = new MembreRole();
|
||||
mr.setMembreOrganisation(newMembreOrganisation());
|
||||
mr.setRole(newRole());
|
||||
mr.setActif(true);
|
||||
mr.setDateDebut(LocalDate.now().minusDays(10));
|
||||
mr.setDateFin(LocalDate.now().minusDays(1));
|
||||
assertThat(mr.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif: true si dans la période")
|
||||
void isActif_true() {
|
||||
@@ -98,4 +121,64 @@ class MembreRoleTest {
|
||||
mr.setRole(newRole());
|
||||
assertThat(mr.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
// ── Branch coverage manquantes ─────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif: dateDebut null → condition L76 false (null short-circuit) → continue vers dateFin")
|
||||
void isActif_dateDebutNull_branchNullShortCircuit() {
|
||||
MembreRole mr = new MembreRole();
|
||||
mr.setMembreOrganisation(newMembreOrganisation());
|
||||
mr.setRole(newRole());
|
||||
mr.setActif(true);
|
||||
mr.setDateDebut(null); // null → dateDebut != null = false → skip if body at L76
|
||||
mr.setDateFin(null); // null → dateFin != null = false → skip if body at L79
|
||||
// → return true
|
||||
assertThat(mr.isActif()).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* L72 branch manquante : getActif() == null → Boolean.TRUE.equals(null) = false
|
||||
* → !false = true → returns false
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("isActif: actif null → Boolean.TRUE.equals(null) false → retourne false")
|
||||
void isActif_actifNull_returnsFalse() {
|
||||
MembreRole mr = new MembreRole();
|
||||
mr.setMembreOrganisation(newMembreOrganisation());
|
||||
mr.setRole(newRole());
|
||||
// actif n'est pas défini (null)
|
||||
mr.setDateDebut(LocalDate.now().minusDays(1));
|
||||
assertThat(mr.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActif: dateDebut non null mais passée → ne retourne pas false (branche false isBefore)")
|
||||
void isActif_dateDebut_notNull_notBefore_continues() {
|
||||
MembreRole mr = new MembreRole();
|
||||
mr.setMembreOrganisation(newMembreOrganisation());
|
||||
mr.setRole(newRole());
|
||||
mr.setActif(true);
|
||||
// dateDebut dans le passé → isBefore(dateDebut) est false → on continue
|
||||
mr.setDateDebut(LocalDate.now().minusDays(5));
|
||||
mr.setDateFin(null);
|
||||
assertThat(mr.isActif()).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* L79 branch manquante : dateFin != null mais today <= dateFin (NOT after)
|
||||
* → `dateFin != null && aujourdhui.isAfter(dateFin)` → false → continue → return true
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("isActif: dateFin non null mais pas encore dépassée → ne retourne pas false (branche false isAfter)")
|
||||
void isActif_dateFin_notNull_notAfter_returnsTrue() {
|
||||
MembreRole mr = new MembreRole();
|
||||
mr.setMembreOrganisation(newMembreOrganisation());
|
||||
mr.setRole(newRole());
|
||||
mr.setActif(true);
|
||||
mr.setDateDebut(LocalDate.now().minusDays(5));
|
||||
// dateFin dans le futur → isAfter(dateFin) est false → retourne true
|
||||
mr.setDateFin(LocalDate.now().plusDays(5));
|
||||
assertThat(mr.isActif()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.communication.MessageStatus;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("Message")
|
||||
class MessageTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("markAsRead sets status to READ and sets readAt")
|
||||
void markAsRead_setsStatusAndReadAt() {
|
||||
Message m = new Message();
|
||||
m.setStatus(MessageStatus.SENT);
|
||||
assertThat(m.getReadAt()).isNull();
|
||||
|
||||
m.markAsRead();
|
||||
|
||||
assertThat(m.getStatus()).isEqualTo(MessageStatus.READ);
|
||||
assertThat(m.getReadAt()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("markAsEdited sets isEdited true and sets editedAt")
|
||||
void markAsEdited_setsIsEditedAndEditedAt() {
|
||||
Message m = new Message();
|
||||
assertThat(m.getIsEdited()).isFalse();
|
||||
assertThat(m.getEditedAt()).isNull();
|
||||
|
||||
m.markAsEdited();
|
||||
|
||||
assertThat(m.getIsEdited()).isTrue();
|
||||
assertThat(m.getEditedAt()).isNotNull();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package dev.lions.unionflow.server.entity;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -95,4 +96,34 @@ class NotificationTest {
|
||||
n.setTypeNotification("EMAIL");
|
||||
assertThat(n.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isEnvoyee: false si statut null")
|
||||
void isEnvoyee_nullStatut_returnsFalse() {
|
||||
Notification n = new Notification();
|
||||
n.setTypeNotification("EMAIL");
|
||||
n.setStatut(null);
|
||||
assertThat(n.isEnvoyee()).isFalse();
|
||||
assertThat(n.isLue()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise les défauts si null")
|
||||
void onCreate_setsDefaults() throws Exception {
|
||||
Notification n = new Notification();
|
||||
n.setTypeNotification("EMAIL");
|
||||
n.setPriorite(null);
|
||||
n.setStatut(null);
|
||||
n.setNombreTentatives(null);
|
||||
n.setDateEnvoiPrevue(null);
|
||||
|
||||
Method onCreate = Notification.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(n);
|
||||
|
||||
assertThat(n.getPriorite()).isEqualTo("NORMALE");
|
||||
assertThat(n.getStatut()).isEqualTo("EN_ATTENTE");
|
||||
assertThat(n.getNombreTentatives()).isEqualTo(0);
|
||||
assertThat(n.getDateEnvoiPrevue()).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,145 +1,805 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("Organisation")
|
||||
@DisplayName("Organisation — couverture complète")
|
||||
class OrganisationTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("getters/setters")
|
||||
void gettersSetters() {
|
||||
// ─── Utilitaire ───────────────────────────────────────────────────────────
|
||||
|
||||
private static Organisation baseOrganisation() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("Club Lions Paris");
|
||||
o.setNomCourt("CL Paris");
|
||||
o.setTypeOrganisation("ASSOCIATION");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("contact@club.fr");
|
||||
o.setTelephone("+33100000000");
|
||||
o.setDevise("XOF");
|
||||
o.setNombreMembres(50);
|
||||
o.setEstOrganisationRacine(true);
|
||||
o.setAccepteNouveauxMembres(true);
|
||||
|
||||
assertThat(o.getNom()).isEqualTo("Club Lions Paris");
|
||||
assertThat(o.getNomCourt()).isEqualTo("CL Paris");
|
||||
assertThat(o.getStatut()).isEqualTo("ACTIVE");
|
||||
assertThat(o.getEmail()).isEqualTo("contact@club.fr");
|
||||
assertThat(o.getNombreMembres()).isEqualTo(50);
|
||||
return o;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getNomComplet: avec et sans nomCourt")
|
||||
void getNomComplet() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("Club A");
|
||||
o.setNomCourt("CA");
|
||||
o.setTypeOrganisation("X");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("a@b.com");
|
||||
assertThat(o.getNomComplet()).isEqualTo("Club A (CA)");
|
||||
o.setNomCourt(null);
|
||||
assertThat(o.getNomComplet()).isEqualTo("Club A");
|
||||
// ─── Getters / Setters ────────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Getters et setters")
|
||||
class GettersSetters {
|
||||
|
||||
@Test
|
||||
@DisplayName("Champs de base")
|
||||
void champsDeBase() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setTelephone("+33100000000");
|
||||
o.setDevise("XOF");
|
||||
o.setNombreMembres(50);
|
||||
o.setEstOrganisationRacine(true);
|
||||
o.setAccepteNouveauxMembres(true);
|
||||
|
||||
assertThat(o.getNom()).isEqualTo("Club Lions Paris");
|
||||
assertThat(o.getNomCourt()).isEqualTo("CL Paris");
|
||||
assertThat(o.getStatut()).isEqualTo("ACTIVE");
|
||||
assertThat(o.getEmail()).isEqualTo("contact@club.fr");
|
||||
assertThat(o.getTelephone()).isEqualTo("+33100000000");
|
||||
assertThat(o.getNombreMembres()).isEqualTo(50);
|
||||
assertThat(o.getEstOrganisationRacine()).isTrue();
|
||||
assertThat(o.getAccepteNouveauxMembres()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("description, numeroEnregistrement, dateFondation")
|
||||
void descriptionEtEnregistrement() {
|
||||
Organisation o = baseOrganisation();
|
||||
LocalDate fondation = LocalDate.of(2010, 6, 15);
|
||||
o.setDescription("Un club de bienfaisance");
|
||||
o.setNumeroEnregistrement("REG-2010-00123");
|
||||
o.setDateFondation(fondation);
|
||||
|
||||
assertThat(o.getDescription()).isEqualTo("Un club de bienfaisance");
|
||||
assertThat(o.getNumeroEnregistrement()).isEqualTo("REG-2010-00123");
|
||||
assertThat(o.getDateFondation()).isEqualTo(fondation);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("telephoneSecondaire et emailSecondaire")
|
||||
void contactsSecondaires() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setTelephoneSecondaire("+33100000099");
|
||||
o.setEmailSecondaire("info@club.fr");
|
||||
|
||||
assertThat(o.getTelephoneSecondaire()).isEqualTo("+33100000099");
|
||||
assertThat(o.getEmailSecondaire()).isEqualTo("info@club.fr");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("adresse, ville, region, pays, codePostal")
|
||||
void adresseComplete() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setAdresse("12 rue de la Paix");
|
||||
o.setVille("Abidjan");
|
||||
o.setRegion("Lagunes");
|
||||
o.setPays("Côte d'Ivoire");
|
||||
o.setCodePostal("01 BP 1234");
|
||||
|
||||
assertThat(o.getAdresse()).isEqualTo("12 rue de la Paix");
|
||||
assertThat(o.getVille()).isEqualTo("Abidjan");
|
||||
assertThat(o.getRegion()).isEqualTo("Lagunes");
|
||||
assertThat(o.getPays()).isEqualTo("Côte d'Ivoire");
|
||||
assertThat(o.getCodePostal()).isEqualTo("01 BP 1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("latitude et longitude")
|
||||
void coordonneesGeographiques() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setLatitude(new BigDecimal("5.354680"));
|
||||
o.setLongitude(new BigDecimal("-4.001430"));
|
||||
|
||||
assertThat(o.getLatitude()).isEqualByComparingTo("5.354680");
|
||||
assertThat(o.getLongitude()).isEqualByComparingTo("-4.001430");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("siteWeb, logo, reseauxSociaux")
|
||||
void webEtReseaux() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setSiteWeb("https://club-lions-paris.fr");
|
||||
o.setLogo("https://cdn.example.com/logo.png");
|
||||
o.setReseauxSociaux("{\"twitter\":\"@clublions\"}");
|
||||
|
||||
assertThat(o.getSiteWeb()).isEqualTo("https://club-lions-paris.fr");
|
||||
assertThat(o.getLogo()).isEqualTo("https://cdn.example.com/logo.png");
|
||||
assertThat(o.getReseauxSociaux()).isEqualTo("{\"twitter\":\"@clublions\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("niveauHierarchique, cheminHierarchique, organisationParente")
|
||||
void hierarchie() {
|
||||
Organisation parent = baseOrganisation();
|
||||
parent.setId(UUID.randomUUID());
|
||||
|
||||
Organisation enfant = baseOrganisation();
|
||||
enfant.setNom("Sous-club Lions");
|
||||
enfant.setEmail("sous@club.fr");
|
||||
enfant.setOrganisationParente(parent);
|
||||
enfant.setNiveauHierarchique(1);
|
||||
enfant.setCheminHierarchique("/" + parent.getId());
|
||||
enfant.setEstOrganisationRacine(false);
|
||||
|
||||
assertThat(enfant.getOrganisationParente()).isEqualTo(parent);
|
||||
assertThat(enfant.getNiveauHierarchique()).isEqualTo(1);
|
||||
assertThat(enfant.getCheminHierarchique()).startsWith("/");
|
||||
assertThat(enfant.getEstOrganisationRacine()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nombreAdministrateurs")
|
||||
void nombreAdministrateurs() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNombreAdministrateurs(5);
|
||||
assertThat(o.getNombreAdministrateurs()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("budgetAnnuel, devise, cotisationObligatoire, montantCotisationAnnuelle")
|
||||
void finances() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setBudgetAnnuel(new BigDecimal("5000000.00"));
|
||||
o.setDevise("XOF");
|
||||
o.setCotisationObligatoire(true);
|
||||
o.setMontantCotisationAnnuelle(new BigDecimal("120000.00"));
|
||||
|
||||
assertThat(o.getBudgetAnnuel()).isEqualByComparingTo("5000000.00");
|
||||
assertThat(o.getDevise()).isEqualTo("XOF");
|
||||
assertThat(o.getCotisationObligatoire()).isTrue();
|
||||
assertThat(o.getMontantCotisationAnnuelle()).isEqualByComparingTo("120000.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("objectifs, activitesPrincipales, certifications, partenaires, notes")
|
||||
void informationsComplementaires() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setObjectifs("Aide humanitaire");
|
||||
o.setActivitesPrincipales("Collecte de fonds, bénévolat");
|
||||
o.setCertifications("ISO 9001");
|
||||
o.setPartenaires("Croix-Rouge, ONU");
|
||||
o.setNotes("Organisation fondée par des bénévoles");
|
||||
|
||||
assertThat(o.getObjectifs()).isEqualTo("Aide humanitaire");
|
||||
assertThat(o.getActivitesPrincipales()).isEqualTo("Collecte de fonds, bénévolat");
|
||||
assertThat(o.getCertifications()).isEqualTo("ISO 9001");
|
||||
assertThat(o.getPartenaires()).isEqualTo("Croix-Rouge, ONU");
|
||||
assertThat(o.getNotes()).isEqualTo("Organisation fondée par des bénévoles");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("organisationPublique")
|
||||
void organisationPublique() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setOrganisationPublique(false);
|
||||
assertThat(o.getOrganisationPublique()).isFalse();
|
||||
o.setOrganisationPublique(true);
|
||||
assertThat(o.getOrganisationPublique()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Relations : membresOrganisations, adresses, comptesWave")
|
||||
void relations() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setMembresOrganisations(new ArrayList<>());
|
||||
o.setAdresses(new ArrayList<>());
|
||||
o.setComptesWave(new ArrayList<>());
|
||||
|
||||
assertThat(o.getMembresOrganisations()).isNotNull().isEmpty();
|
||||
assertThat(o.getAdresses()).isNotNull().isEmpty();
|
||||
assertThat(o.getComptesWave()).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Champs hérités de BaseEntity (id, dateCreation, creePar, modifiePar, version, actif)")
|
||||
void champsBaseEntity() {
|
||||
UUID id = UUID.randomUUID();
|
||||
Organisation o = baseOrganisation();
|
||||
o.setId(id);
|
||||
o.setCreePar("admin@unionflow.dev");
|
||||
o.setModifiePar("manager@unionflow.dev");
|
||||
o.setVersion(2L);
|
||||
o.setActif(true);
|
||||
|
||||
assertThat(o.getId()).isEqualTo(id);
|
||||
assertThat(o.getCreePar()).isEqualTo("admin@unionflow.dev");
|
||||
assertThat(o.getModifiePar()).isEqualTo("manager@unionflow.dev");
|
||||
assertThat(o.getVersion()).isEqualTo(2L);
|
||||
assertThat(o.getActif()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAncienneteAnnees et isRecente")
|
||||
void anciennete() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("X");
|
||||
o.setTypeOrganisation("X");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("x@y.com");
|
||||
o.setDateFondation(LocalDate.now().minusYears(5));
|
||||
assertThat(o.getAncienneteAnnees()).isEqualTo(5);
|
||||
assertThat(o.isRecente()).isFalse();
|
||||
o.setDateFondation(LocalDate.now().minusYears(1));
|
||||
assertThat(o.isRecente()).isTrue();
|
||||
// ─── Builder ──────────────────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Builder Lombok")
|
||||
class BuilderTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Builder avec champs obligatoires")
|
||||
void builderChampsObligatoires() {
|
||||
Organisation o = Organisation.builder()
|
||||
.nom("Association Solidarité")
|
||||
.typeOrganisation("ASSOCIATION")
|
||||
.statut("ACTIVE")
|
||||
.email("contact@solidarite.ci")
|
||||
.build();
|
||||
|
||||
assertThat(o.getNom()).isEqualTo("Association Solidarité");
|
||||
assertThat(o.getTypeOrganisation()).isEqualTo("ASSOCIATION");
|
||||
assertThat(o.getStatut()).isEqualTo("ACTIVE");
|
||||
assertThat(o.getEmail()).isEqualTo("contact@solidarite.ci");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Builder : valeurs @Builder.Default")
|
||||
void builderDefaults() {
|
||||
Organisation o = Organisation.builder()
|
||||
.nom("Coopérative Test")
|
||||
.typeOrganisation("COOPERATIVE")
|
||||
.statut("ACTIVE")
|
||||
.email("coop@test.ci")
|
||||
.build();
|
||||
|
||||
assertThat(o.getNiveauHierarchique()).isEqualTo(0);
|
||||
assertThat(o.getEstOrganisationRacine()).isTrue();
|
||||
assertThat(o.getNombreMembres()).isEqualTo(0);
|
||||
assertThat(o.getNombreAdministrateurs()).isEqualTo(0);
|
||||
assertThat(o.getDevise()).isEqualTo("XOF");
|
||||
assertThat(o.getCotisationObligatoire()).isFalse();
|
||||
assertThat(o.getOrganisationPublique()).isTrue();
|
||||
assertThat(o.getAccepteNouveauxMembres()).isTrue();
|
||||
assertThat(o.getMembresOrganisations()).isNotNull().isEmpty();
|
||||
assertThat(o.getAdresses()).isNotNull().isEmpty();
|
||||
assertThat(o.getComptesWave()).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Builder avec tous les champs facultatifs")
|
||||
void builderChampsOptionnels() {
|
||||
LocalDate fondation = LocalDate.of(2000, 1, 1);
|
||||
Organisation parent = baseOrganisation();
|
||||
parent.setId(UUID.randomUUID());
|
||||
|
||||
Organisation o = Organisation.builder()
|
||||
.nom("Lions Club Abidjan")
|
||||
.nomCourt("LCA")
|
||||
.typeOrganisation("LIONS")
|
||||
.statut("ACTIVE")
|
||||
.email("lions@abidjan.ci")
|
||||
.telephone("+22520000000")
|
||||
.telephoneSecondaire("+22521000000")
|
||||
.emailSecondaire("info@lions.ci")
|
||||
.description("Club Lions International")
|
||||
.dateFondation(fondation)
|
||||
.numeroEnregistrement("LCA-2000-001")
|
||||
.adresse("Plateau, Av. de la République")
|
||||
.ville("Abidjan")
|
||||
.region("Lagunes")
|
||||
.pays("Côte d'Ivoire")
|
||||
.codePostal("01 BP 100")
|
||||
.latitude(new BigDecimal("5.354"))
|
||||
.longitude(new BigDecimal("-4.001"))
|
||||
.siteWeb("https://lions.ci")
|
||||
.logo("https://cdn.lions.ci/logo.png")
|
||||
.reseauxSociaux("{\"facebook\":\"lionsabidjan\"}")
|
||||
.organisationParente(parent)
|
||||
.niveauHierarchique(1)
|
||||
.estOrganisationRacine(false)
|
||||
.cheminHierarchique("/" + parent.getId())
|
||||
.nombreMembres(120)
|
||||
.nombreAdministrateurs(8)
|
||||
.budgetAnnuel(new BigDecimal("10000000.00"))
|
||||
.devise("XOF")
|
||||
.cotisationObligatoire(true)
|
||||
.montantCotisationAnnuelle(new BigDecimal("100000.00"))
|
||||
.objectifs("Service humanitaire")
|
||||
.activitesPrincipales("Actions sociales")
|
||||
.certifications("Lions International")
|
||||
.partenaires("UNICEF")
|
||||
.notes("Fondé par des membres engagés")
|
||||
.organisationPublique(true)
|
||||
.accepteNouveauxMembres(true)
|
||||
.build();
|
||||
|
||||
assertThat(o.getNomCourt()).isEqualTo("LCA");
|
||||
assertThat(o.getDescription()).isEqualTo("Club Lions International");
|
||||
assertThat(o.getDateFondation()).isEqualTo(fondation);
|
||||
assertThat(o.getNumeroEnregistrement()).isEqualTo("LCA-2000-001");
|
||||
assertThat(o.getVille()).isEqualTo("Abidjan");
|
||||
assertThat(o.getPays()).isEqualTo("Côte d'Ivoire");
|
||||
assertThat(o.getLatitude()).isEqualByComparingTo("5.354");
|
||||
assertThat(o.getLongitude()).isEqualByComparingTo("-4.001");
|
||||
assertThat(o.getSiteWeb()).isEqualTo("https://lions.ci");
|
||||
assertThat(o.getOrganisationParente()).isEqualTo(parent);
|
||||
assertThat(o.getNiveauHierarchique()).isEqualTo(1);
|
||||
assertThat(o.getEstOrganisationRacine()).isFalse();
|
||||
assertThat(o.getNombreMembres()).isEqualTo(120);
|
||||
assertThat(o.getBudgetAnnuel()).isEqualByComparingTo("10000000.00");
|
||||
assertThat(o.getCotisationObligatoire()).isTrue();
|
||||
assertThat(o.getMontantCotisationAnnuelle()).isEqualByComparingTo("100000.00");
|
||||
assertThat(o.getObjectifs()).isEqualTo("Service humanitaire");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActive")
|
||||
void isActive() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("X");
|
||||
o.setTypeOrganisation("X");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("x@y.com");
|
||||
o.setActif(true);
|
||||
assertThat(o.isActive()).isTrue();
|
||||
o.setStatut("SUSPENDUE");
|
||||
assertThat(o.isActive()).isFalse();
|
||||
// ─── Méthodes métier ──────────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Méthodes métier")
|
||||
class MethodesMetier {
|
||||
|
||||
@Test
|
||||
@DisplayName("getNomComplet() avec nomCourt non vide")
|
||||
void getNomComplet_avecNomCourt() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNomCourt("CL Paris");
|
||||
assertThat(o.getNomComplet()).isEqualTo("Club Lions Paris (CL Paris)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getNomComplet() avec nomCourt=null")
|
||||
void getNomComplet_nomCourtNull() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNomCourt(null);
|
||||
assertThat(o.getNomComplet()).isEqualTo("Club Lions Paris");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getNomComplet() avec nomCourt vide ('')")
|
||||
void getNomComplet_nomCourtVide() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNomCourt("");
|
||||
assertThat(o.getNomComplet()).isEqualTo("Club Lions Paris");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAncienneteAnnees() avec dateFondation=null retourne 0")
|
||||
void anciennete_dateFondationNull_retourneZero() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setDateFondation(null);
|
||||
assertThat(o.getAncienneteAnnees()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getAncienneteAnnees() avec dateFondation il y a 5 ans retourne 5")
|
||||
void anciennete_cincAns_retourneCinq() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setDateFondation(LocalDate.now().minusYears(5));
|
||||
assertThat(o.getAncienneteAnnees()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isRecente() retourne false si ancienneté >= 2 ans")
|
||||
void isRecente_ancienneOrganisation_retourneFalse() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setDateFondation(LocalDate.now().minusYears(3));
|
||||
assertThat(o.isRecente()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isRecente() retourne true si ancienneté < 2 ans")
|
||||
void isRecente_jeuneOrganisation_retourneTrue() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setDateFondation(LocalDate.now().minusYears(1));
|
||||
assertThat(o.isRecente()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isRecente() retourne true si dateFondation=null (ancienneté=0 < 2)")
|
||||
void isRecente_dateFondationNull_retourneTrue() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setDateFondation(null);
|
||||
assertThat(o.isRecente()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActive() retourne true si statut=ACTIVE et actif=true")
|
||||
void isActive_activeEtActif_retourneTrue() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setStatut("ACTIVE");
|
||||
o.setActif(true);
|
||||
assertThat(o.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActive() retourne false si statut=SUSPENDUE")
|
||||
void isActive_suspendue_retourneFalse() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setStatut("SUSPENDUE");
|
||||
o.setActif(true);
|
||||
assertThat(o.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActive() retourne false si actif=false")
|
||||
void isActive_inactif_retourneFalse() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setStatut("ACTIVE");
|
||||
o.setActif(false);
|
||||
assertThat(o.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActive() retourne false si actif=null")
|
||||
void isActive_actifNull_retourneFalse() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setStatut("ACTIVE");
|
||||
o.setActif(null);
|
||||
assertThat(o.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ajouterMembre() incrémente nombreMembres")
|
||||
void ajouterMembre_incremente() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNombreMembres(10);
|
||||
o.ajouterMembre();
|
||||
assertThat(o.getNombreMembres()).isEqualTo(11);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ajouterMembre() avec nombreMembres=null initialise à 1")
|
||||
void ajouterMembre_null_initialiseeA1() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNombreMembres(null);
|
||||
o.ajouterMembre();
|
||||
assertThat(o.getNombreMembres()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("retirerMembre() décrémente nombreMembres")
|
||||
void retirerMembre_decremente() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNombreMembres(10);
|
||||
o.retirerMembre();
|
||||
assertThat(o.getNombreMembres()).isEqualTo(9);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("retirerMembre() ne passe pas sous 0 quand nombreMembres=0")
|
||||
void retirerMembre_zero_resteAZero() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNombreMembres(0);
|
||||
o.retirerMembre();
|
||||
assertThat(o.getNombreMembres()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("retirerMembre() ne fait rien si nombreMembres=null")
|
||||
void retirerMembre_null_neFaitRien() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNombreMembres(null);
|
||||
o.retirerMembre();
|
||||
assertThat(o.getNombreMembres()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("activer() met statut=ACTIVE et actif=true")
|
||||
void activer_metAJourStatutEtActif() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setStatut("SUSPENDUE");
|
||||
o.setActif(false);
|
||||
o.activer("admin@unionflow.dev");
|
||||
|
||||
assertThat(o.getStatut()).isEqualTo("ACTIVE");
|
||||
assertThat(o.getActif()).isTrue();
|
||||
assertThat(o.getModifiePar()).isEqualTo("admin@unionflow.dev");
|
||||
assertThat(o.getDateModification()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("suspendre() met statut=SUSPENDUE et accepteNouveauxMembres=false")
|
||||
void suspendre_metAJourStatutEtMembres() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setAccepteNouveauxMembres(true);
|
||||
o.suspendre("manager@unionflow.dev");
|
||||
|
||||
assertThat(o.getStatut()).isEqualTo("SUSPENDUE");
|
||||
assertThat(o.getAccepteNouveauxMembres()).isFalse();
|
||||
assertThat(o.getModifiePar()).isEqualTo("manager@unionflow.dev");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("dissoudre() met statut=DISSOUTE, actif=false, accepteNouveauxMembres=false")
|
||||
void dissoudre_metAJourTousLesChamps() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setActif(true);
|
||||
o.setAccepteNouveauxMembres(true);
|
||||
o.dissoudre("liquidateur@unionflow.dev");
|
||||
|
||||
assertThat(o.getStatut()).isEqualTo("DISSOUTE");
|
||||
assertThat(o.getActif()).isFalse();
|
||||
assertThat(o.getAccepteNouveauxMembres()).isFalse();
|
||||
assertThat(o.getModifiePar()).isEqualTo("liquidateur@unionflow.dev");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ajouterMembre et retirerMembre")
|
||||
void ajouterRetirerMembre() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("X");
|
||||
o.setTypeOrganisation("X");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("x@y.com");
|
||||
o.setNombreMembres(10);
|
||||
o.ajouterMembre();
|
||||
assertThat(o.getNombreMembres()).isEqualTo(11);
|
||||
o.retirerMembre();
|
||||
o.retirerMembre();
|
||||
assertThat(o.getNombreMembres()).isEqualTo(9);
|
||||
// ─── @PrePersist onCreate ─────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("@PrePersist onCreate()")
|
||||
class OnCreate {
|
||||
|
||||
@Test
|
||||
@DisplayName("statut=null est initialisé à ACTIVE")
|
||||
void statut_null_initialiseeAACtive() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setStatut(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getStatut()).isEqualTo("ACTIVE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("statut déjà défini n'est pas écrasé")
|
||||
void statut_dejaDefini_nonEcrase() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setStatut("SUSPENDUE");
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getStatut()).isEqualTo("SUSPENDUE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("typeOrganisation=null est initialisé à ASSOCIATION")
|
||||
void typeOrganisation_null_initialiseeAAssociation() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setTypeOrganisation(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getTypeOrganisation()).isEqualTo("ASSOCIATION");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("typeOrganisation déjà défini n'est pas écrasé")
|
||||
void typeOrganisation_dejaDefini_nonEcrase() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setTypeOrganisation("COOPERATIVE");
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getTypeOrganisation()).isEqualTo("COOPERATIVE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("devise=null est initialisé à XOF")
|
||||
void devise_null_initialiseeAXof() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setDevise(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getDevise()).isEqualTo("XOF");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("devise déjà défini n'est pas écrasé")
|
||||
void devise_dejaDefinie_nonEcrasee() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setDevise("EUR");
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getDevise()).isEqualTo("EUR");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("niveauHierarchique=null est initialisé à 0")
|
||||
void niveauHierarchique_null_initialiseeAZero() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNiveauHierarchique(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getNiveauHierarchique()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estOrganisationRacine=null sans parent est initialisé à true")
|
||||
void estOrganisationRacine_null_sansParent_initialiseeATrue() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setEstOrganisationRacine(null);
|
||||
o.setOrganisationParente(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getEstOrganisationRacine()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estOrganisationRacine=null avec parent est initialisé à false")
|
||||
void estOrganisationRacine_null_avecParent_initialiseeAFalse() {
|
||||
Organisation parent = baseOrganisation();
|
||||
parent.setId(UUID.randomUUID());
|
||||
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNom("Sous-club");
|
||||
o.setEmail("sous@club.fr");
|
||||
o.setEstOrganisationRacine(null);
|
||||
o.setOrganisationParente(parent);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getEstOrganisationRacine()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estOrganisationRacine déjà défini n'est pas écrasé")
|
||||
void estOrganisationRacine_dejaDefini_nonEcrase() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setEstOrganisationRacine(false);
|
||||
o.setOrganisationParente(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getEstOrganisationRacine()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nombreMembres=null est initialisé à 0")
|
||||
void nombreMembres_null_initialiseeAZero() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNombreMembres(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getNombreMembres()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nombreAdministrateurs=null est initialisé à 0")
|
||||
void nombreAdministrateurs_null_initialiseeAZero() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setNombreAdministrateurs(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getNombreAdministrateurs()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("organisationPublique=null est initialisé à true")
|
||||
void organisationPublique_null_initialiseeATrue() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setOrganisationPublique(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getOrganisationPublique()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("accepteNouveauxMembres=null est initialisé à true")
|
||||
void accepteNouveauxMembres_null_initialiseeATrue() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setAccepteNouveauxMembres(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getAccepteNouveauxMembres()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cotisationObligatoire=null est initialisé à false")
|
||||
void cotisationObligatoire_null_initialiseeAFalse() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setCotisationObligatoire(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getCotisationObligatoire()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() initialise aussi dateCreation et actif via BaseEntity")
|
||||
void onCreateInitialiseBaseEntity() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.setDateCreation(null);
|
||||
o.setActif(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getDateCreation()).isNotNull();
|
||||
assertThat(o.getActif()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() avec tous les champs null initialise toutes les valeurs par défaut")
|
||||
void onCreateTousChampNulls_initialiseDefaults() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("Test Org");
|
||||
o.setEmail("test@org.ci");
|
||||
// Tous les @Builder.Default sont null via new()
|
||||
o.setStatut(null);
|
||||
o.setTypeOrganisation(null);
|
||||
o.setDevise(null);
|
||||
o.setNiveauHierarchique(null);
|
||||
o.setEstOrganisationRacine(null);
|
||||
o.setOrganisationParente(null);
|
||||
o.setNombreMembres(null);
|
||||
o.setNombreAdministrateurs(null);
|
||||
o.setOrganisationPublique(null);
|
||||
o.setAccepteNouveauxMembres(null);
|
||||
o.setCotisationObligatoire(null);
|
||||
o.onCreate();
|
||||
|
||||
assertThat(o.getStatut()).isEqualTo("ACTIVE");
|
||||
assertThat(o.getTypeOrganisation()).isEqualTo("ASSOCIATION");
|
||||
assertThat(o.getDevise()).isEqualTo("XOF");
|
||||
assertThat(o.getNiveauHierarchique()).isEqualTo(0);
|
||||
assertThat(o.getEstOrganisationRacine()).isTrue();
|
||||
assertThat(o.getNombreMembres()).isEqualTo(0);
|
||||
assertThat(o.getNombreAdministrateurs()).isEqualTo(0);
|
||||
assertThat(o.getOrganisationPublique()).isTrue();
|
||||
assertThat(o.getAccepteNouveauxMembres()).isTrue();
|
||||
assertThat(o.getCotisationObligatoire()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("activer, suspendre, dissoudre")
|
||||
void activerSuspendreDissoudre() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("X");
|
||||
o.setTypeOrganisation("X");
|
||||
o.setStatut("SUSPENDUE");
|
||||
o.setEmail("x@y.com");
|
||||
o.activer("admin@test.com");
|
||||
assertThat(o.getStatut()).isEqualTo("ACTIVE");
|
||||
assertThat(o.getActif()).isTrue();
|
||||
o.suspendre("admin@test.com");
|
||||
assertThat(o.getStatut()).isEqualTo("SUSPENDUE");
|
||||
assertThat(o.getAccepteNouveauxMembres()).isFalse();
|
||||
o.dissoudre("admin@test.com");
|
||||
assertThat(o.getStatut()).isEqualTo("DISSOUTE");
|
||||
assertThat(o.getActif()).isFalse();
|
||||
// ─── Equals / HashCode / toString ────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("equals, hashCode et toString")
|
||||
class EgaliteEtToString {
|
||||
|
||||
@Test
|
||||
@DisplayName("Deux instances avec le même id sont égales")
|
||||
void equals_memeId_egales() {
|
||||
UUID id = UUID.randomUUID();
|
||||
Organisation a = buildOrganisation(id, "N", "e@e.com");
|
||||
Organisation b = buildOrganisation(id, "N", "e@e.com");
|
||||
|
||||
assertThat(a).isEqualTo(b);
|
||||
assertThat(a.hashCode()).isEqualTo(b.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deux instances avec des id différents ne sont pas égales")
|
||||
void equals_idsDifferents_pasEgales() {
|
||||
Organisation a = buildOrganisation(UUID.randomUUID(), "N1", "e1@e.com");
|
||||
Organisation b = buildOrganisation(UUID.randomUUID(), "N2", "e2@e.com");
|
||||
|
||||
assertThat(a).isNotEqualTo(b);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toString() n'est pas null ni vide")
|
||||
void toString_nonNullNonVide() {
|
||||
Organisation o = baseOrganisation();
|
||||
assertThat(o.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toString() contient le nom de l'organisation")
|
||||
void toString_contientNom() {
|
||||
Organisation o = baseOrganisation();
|
||||
assertThat(o.toString()).contains("Club Lions Paris");
|
||||
}
|
||||
|
||||
private Organisation buildOrganisation(UUID id, String nom, String email) {
|
||||
Organisation o = new Organisation();
|
||||
o.setId(id);
|
||||
o.setNom(nom);
|
||||
o.setTypeOrganisation("X");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail(email);
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("equals et hashCode")
|
||||
void equalsHashCode() {
|
||||
UUID id = UUID.randomUUID();
|
||||
Organisation a = new Organisation();
|
||||
a.setId(id);
|
||||
a.setNom("N");
|
||||
a.setTypeOrganisation("X");
|
||||
a.setStatut("ACTIVE");
|
||||
a.setEmail("e@e.com");
|
||||
Organisation b = new Organisation();
|
||||
b.setId(id);
|
||||
b.setNom("N");
|
||||
b.setTypeOrganisation("X");
|
||||
b.setStatut("ACTIVE");
|
||||
b.setEmail("e@e.com");
|
||||
assertThat(a).isEqualTo(b);
|
||||
assertThat(a.hashCode()).isEqualTo(b.hashCode());
|
||||
}
|
||||
// ─── marquerCommeModifie (hérité de BaseEntity) ──────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("toString non null")
|
||||
void toString_nonNull() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("X");
|
||||
o.setTypeOrganisation("X");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("x@y.com");
|
||||
assertThat(o.toString()).isNotNull().isNotEmpty();
|
||||
@Nested
|
||||
@DisplayName("marquerCommeModifie() — hérité de BaseEntity")
|
||||
class MarquerCommeModifie {
|
||||
|
||||
@Test
|
||||
@DisplayName("marquerCommeModifie() met à jour dateModification et modifiePar")
|
||||
void marquerCommeModifie_metAJourChamps() {
|
||||
Organisation o = baseOrganisation();
|
||||
o.marquerCommeModifie("operateur@unionflow.dev");
|
||||
|
||||
assertThat(o.getDateModification()).isNotNull();
|
||||
assertThat(o.getModifiePar()).isEqualTo("operateur@unionflow.dev");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,4 +78,32 @@ class PaiementObjetTest {
|
||||
po.setMontantApplique(BigDecimal.ONE);
|
||||
assertThat(po.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate initialise dateApplication si null")
|
||||
void onCreate_setsDateApplicationWhenNull() {
|
||||
PaiementObjet po = new PaiementObjet();
|
||||
po.setPaiement(newPaiement());
|
||||
po.setTypeObjetCible("COTISATION");
|
||||
po.setObjetCibleId(UUID.randomUUID());
|
||||
po.setMontantApplique(BigDecimal.ONE);
|
||||
// dateApplication est null
|
||||
|
||||
po.onCreate();
|
||||
|
||||
assertThat(po.getDateApplication()).isNotNull();
|
||||
assertThat(po.getActif()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate ne remplace pas une dateApplication existante")
|
||||
void onCreate_doesNotOverrideDateApplication() {
|
||||
LocalDateTime fixed = LocalDateTime.of(2026, 1, 1, 0, 0);
|
||||
PaiementObjet po = new PaiementObjet();
|
||||
po.setDateApplication(fixed);
|
||||
|
||||
po.onCreate();
|
||||
|
||||
assertThat(po.getDateApplication()).isEqualTo(fixed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package dev.lions.unionflow.server.entity;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -44,6 +45,64 @@ class PermissionTest {
|
||||
assertThat(p.isCodeValide()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isCodeValide: false si code null")
|
||||
void isCodeValide_null() {
|
||||
Permission p = new Permission();
|
||||
p.setCode(null);
|
||||
assertThat(p.isCodeValide()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isCodeValide: false si contient ' > ' mais moins de 3 parties")
|
||||
void isCodeValide_twoPartsOnly() {
|
||||
Permission p = new Permission();
|
||||
p.setCode("A > B");
|
||||
assertThat(p.isCodeValide()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: code null + module/ressource/action renseignés → code généré")
|
||||
void onCreate_generatesCode_whenNullAndComponentsPresent() throws Exception {
|
||||
Permission p = new Permission();
|
||||
p.setCode(null);
|
||||
p.setModule("org");
|
||||
p.setRessource("membre");
|
||||
p.setAction("create");
|
||||
Method onCreate = Permission.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(p);
|
||||
assertThat(p.getCode()).isEqualTo("ORG > MEMBRE > CREATE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: code null + composants null → code reste null")
|
||||
void onCreate_codeRemainsNull_whenComponentsNull() throws Exception {
|
||||
Permission p = new Permission();
|
||||
p.setCode(null);
|
||||
p.setModule(null);
|
||||
p.setRessource(null);
|
||||
p.setAction(null);
|
||||
Method onCreate = Permission.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(p);
|
||||
assertThat(p.getCode()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: code déjà renseigné → non écrasé")
|
||||
void onCreate_existingCode_notOverwritten() throws Exception {
|
||||
Permission p = new Permission();
|
||||
p.setCode("X > Y > Z");
|
||||
p.setModule("other");
|
||||
p.setRessource("other");
|
||||
p.setAction("other");
|
||||
Method onCreate = Permission.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(p);
|
||||
assertThat(p.getCode()).isEqualTo("X > Y > Z");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("equals et hashCode")
|
||||
void equalsHashCode() {
|
||||
@@ -74,4 +133,61 @@ class PermissionTest {
|
||||
p.setAction("Z");
|
||||
assertThat(p.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
// ── Branch coverage manquantes ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* L85 branch manquante : code = "" (not null but isEmpty → true) avec composants présents
|
||||
* → couvre la branche `code != null && code.isEmpty()` (deuxième branche du ||)
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("onCreate: code vide (empty) + composants présents → code généré (branche isEmpty)")
|
||||
void onCreate_emptyCode_withComponents_generatesCode() throws Exception {
|
||||
Permission p = new Permission();
|
||||
p.setCode("");
|
||||
p.setModule("org");
|
||||
p.setRessource("membre");
|
||||
p.setAction("read");
|
||||
Method onCreate = Permission.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(p);
|
||||
assertThat(p.getCode()).isEqualTo("ORG > MEMBRE > READ");
|
||||
}
|
||||
|
||||
/**
|
||||
* L86 branch manquante : module != null, ressource != null, action == null
|
||||
* → la condition `module != null && ressource != null && action != null` est false (action null)
|
||||
* → code reste null
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("onCreate: module et ressource présents, action null → code reste null (branche action null)")
|
||||
void onCreate_moduleAndRessourcePresent_actionNull_codeRemainsNull() throws Exception {
|
||||
Permission p = new Permission();
|
||||
p.setCode(null);
|
||||
p.setModule("org");
|
||||
p.setRessource("membre");
|
||||
p.setAction(null);
|
||||
Method onCreate = Permission.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(p);
|
||||
assertThat(p.getCode()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* L86 branch manquante : module != null, ressource == null
|
||||
* → la condition `module != null && ressource != null && action != null` est false (ressource null)
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("onCreate: module présent, ressource null → code reste null (branche ressource null)")
|
||||
void onCreate_modulePresent_ressourceNull_codeRemainsNull() throws Exception {
|
||||
Permission p = new Permission();
|
||||
p.setCode(null);
|
||||
p.setModule("org");
|
||||
p.setRessource(null);
|
||||
p.setAction("create");
|
||||
Method onCreate = Permission.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(p);
|
||||
assertThat(p.getCode()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.abonnement.StatutSouscription;
|
||||
import dev.lions.unionflow.server.api.enums.abonnement.TypePeriodeAbonnement;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests de couverture pour SouscriptionOrganisation.
|
||||
* Couvre les branches manquantes de onCreate() et decrementerQuota().
|
||||
*/
|
||||
@DisplayName("SouscriptionOrganisation - branches manquantes")
|
||||
class SouscriptionOrganisationBranchTest {
|
||||
|
||||
// ── onCreate() ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Branch manquante : formule != null mais quotaMax est déjà renseigné
|
||||
* → la ligne `quotaMax = formule.getMaxMembres()` n'est PAS exécutée.
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("onCreate: formule présente mais quotaMax déjà défini → quotaMax conservé")
|
||||
void onCreate_formuleNonNull_quotaMaxDejaDefini_conserveQuotaMax() throws Exception {
|
||||
FormuleAbonnement formule = new FormuleAbonnement();
|
||||
formule.setMaxMembres(50);
|
||||
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setDateDebut(LocalDate.now());
|
||||
s.setDateFin(LocalDate.now().plusMonths(1));
|
||||
s.setFormule(formule);
|
||||
s.setQuotaMax(100); // déjà défini → ne doit PAS être écrasé
|
||||
|
||||
Method onCreate = SouscriptionOrganisation.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(s);
|
||||
|
||||
// quotaMax reste 100, pas remplacé par formule.getMaxMembres() (50)
|
||||
assertThat(s.getQuotaMax()).isEqualTo(100);
|
||||
// les autres defaults sont positionnés
|
||||
assertThat(s.getStatut()).isEqualTo(StatutSouscription.ACTIVE);
|
||||
assertThat(s.getTypePeriode()).isEqualTo(TypePeriodeAbonnement.MENSUEL);
|
||||
assertThat(s.getQuotaUtilise()).isEqualTo(0);
|
||||
}
|
||||
|
||||
// ── decrementerQuota() ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Branch manquante : quotaUtilise == 0 → la condition
|
||||
* `quotaUtilise != null && quotaUtilise > 0` est false, le quota ne change pas.
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("decrementerQuota: quotaUtilise=0 → reste 0 (pas de décrément)")
|
||||
void decrementerQuota_quotaUtiliseZero_resterAZero() {
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setDateDebut(LocalDate.now());
|
||||
s.setDateFin(LocalDate.now().plusMonths(1));
|
||||
s.setQuotaUtilise(0);
|
||||
|
||||
s.decrementerQuota();
|
||||
|
||||
assertThat(s.getQuotaUtilise()).isEqualTo(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Branche couverte pour référence : quotaUtilise null → décrement ignoré.
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("decrementerQuota: quotaUtilise=null → reste null")
|
||||
void decrementerQuota_quotaUtiliseNull_resterNull() {
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setDateDebut(LocalDate.now());
|
||||
s.setDateFin(LocalDate.now().plusMonths(1));
|
||||
s.setQuotaUtilise(null);
|
||||
|
||||
s.decrementerQuota();
|
||||
|
||||
assertThat(s.getQuotaUtilise()).isNull();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import dev.lions.unionflow.server.api.enums.abonnement.TypePeriodeAbonnement;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -132,4 +133,113 @@ class SouscriptionOrganisationTest {
|
||||
s.setDateFin(LocalDate.now().plusYears(1));
|
||||
assertThat(s.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isQuotaDepasse: false si quotaMax null")
|
||||
void isQuotaDepasse_quotaMaxNull_returnsFalse() {
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setOrganisation(newOrganisation());
|
||||
s.setFormule(newFormule());
|
||||
s.setQuotaMax(null);
|
||||
s.setQuotaUtilise(100);
|
||||
assertThat(s.isQuotaDepasse()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getPlacesRestantes: MAX_VALUE si quotaMax null")
|
||||
void getPlacesRestantes_quotaMaxNull_returnsMaxInt() {
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setOrganisation(newOrganisation());
|
||||
s.setFormule(newFormule());
|
||||
s.setQuotaMax(null);
|
||||
assertThat(s.getPlacesRestantes()).isEqualTo(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("incrementerQuota: quotaUtilise null → initialisé à 1")
|
||||
void incrementerQuota_nullQuota_initializesTo1() {
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setOrganisation(newOrganisation());
|
||||
s.setFormule(newFormule());
|
||||
s.setQuotaUtilise(null);
|
||||
s.incrementerQuota();
|
||||
assertThat(s.getQuotaUtilise()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("decrementerQuota: quotaUtilise=0 → ne descend pas")
|
||||
void decrementerQuota_zeroQuota_noChange() {
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setOrganisation(newOrganisation());
|
||||
s.setFormule(newFormule());
|
||||
s.setQuotaUtilise(0);
|
||||
s.decrementerQuota();
|
||||
assertThat(s.getQuotaUtilise()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isActive: false si statut SUSPENDUE")
|
||||
void isActive_statusSuspendue_returnsFalse() {
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setOrganisation(newOrganisation());
|
||||
s.setFormule(newFormule());
|
||||
s.setDateDebut(LocalDate.now().minusMonths(1));
|
||||
s.setDateFin(LocalDate.now().plusMonths(1));
|
||||
s.setStatut(StatutSouscription.SUSPENDUE);
|
||||
assertThat(s.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise les défauts si null, copie quotaMax depuis formule")
|
||||
void onCreate_setsDefaults() throws Exception {
|
||||
FormuleAbonnement formule = newFormule(); // maxMembres=100
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setOrganisation(newOrganisation());
|
||||
s.setFormule(formule);
|
||||
s.setDateDebut(LocalDate.now());
|
||||
s.setDateFin(LocalDate.now().plusYears(1));
|
||||
s.setStatut(null);
|
||||
s.setTypePeriode(null);
|
||||
s.setQuotaUtilise(null);
|
||||
s.setQuotaMax(null);
|
||||
|
||||
Method onCreate = SouscriptionOrganisation.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(s);
|
||||
|
||||
assertThat(s.getStatut()).isEqualTo(StatutSouscription.ACTIVE);
|
||||
assertThat(s.getTypePeriode()).isEqualTo(TypePeriodeAbonnement.MENSUEL);
|
||||
assertThat(s.getQuotaUtilise()).isEqualTo(0);
|
||||
assertThat(s.getQuotaMax()).isEqualTo(100);
|
||||
}
|
||||
|
||||
// ── Branch coverage manquante ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* L118 branch manquante : formule == null → `if (formule != null && quotaMax == null)` → false (court-circuit)
|
||||
* → quotaMax reste null (ou tel quel), pas de NPE
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("onCreate: formule == null → la branche quotaMax depuis formule n'est pas exécutée")
|
||||
void onCreate_formulesNull_quotaMaxUnchanged() throws Exception {
|
||||
SouscriptionOrganisation s = new SouscriptionOrganisation();
|
||||
s.setOrganisation(newOrganisation());
|
||||
s.setFormule(null); // formule == null → branche false (court-circuit)
|
||||
s.setDateDebut(LocalDate.now());
|
||||
s.setDateFin(LocalDate.now().plusYears(1));
|
||||
s.setStatut(null);
|
||||
s.setTypePeriode(null);
|
||||
s.setQuotaUtilise(null);
|
||||
s.setQuotaMax(null);
|
||||
|
||||
Method onCreate = SouscriptionOrganisation.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(s);
|
||||
|
||||
// les défauts sont positionnés, mais quotaMax reste null (formule est null)
|
||||
assertThat(s.getStatut()).isEqualTo(StatutSouscription.ACTIVE);
|
||||
assertThat(s.getTypePeriode()).isEqualTo(TypePeriodeAbonnement.MENSUEL);
|
||||
assertThat(s.getQuotaUtilise()).isEqualTo(0);
|
||||
assertThat(s.getQuotaMax()).isNull(); // formule null → quotaMax non initialisé
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package dev.lions.unionflow.server.entity;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -26,6 +27,42 @@ class TemplateNotificationTest {
|
||||
assertThat(t.getLangue()).isEqualTo("fr");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: langue null → initialisée à 'fr'")
|
||||
void onCreate_langue_null_setsDefault() throws Exception {
|
||||
TemplateNotification t = new TemplateNotification();
|
||||
t.setCode("T1");
|
||||
t.setLangue(null);
|
||||
Method onCreate = TemplateNotification.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(t);
|
||||
assertThat(t.getLangue()).isEqualTo("fr");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: langue vide → initialisée à 'fr'")
|
||||
void onCreate_langue_empty_setsDefault() throws Exception {
|
||||
TemplateNotification t = new TemplateNotification();
|
||||
t.setCode("T2");
|
||||
t.setLangue("");
|
||||
Method onCreate = TemplateNotification.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(t);
|
||||
assertThat(t.getLangue()).isEqualTo("fr");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: langue déjà renseignée → non écrasée")
|
||||
void onCreate_langue_alreadySet_notOverwritten() throws Exception {
|
||||
TemplateNotification t = new TemplateNotification();
|
||||
t.setCode("T3");
|
||||
t.setLangue("en");
|
||||
Method onCreate = TemplateNotification.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(t);
|
||||
assertThat(t.getLangue()).isEqualTo("en");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("equals et hashCode")
|
||||
void equalsHashCode() {
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("TransactionApproval")
|
||||
class TransactionApprovalTest {
|
||||
|
||||
private static Organisation newOrganisation() {
|
||||
Organisation o = new Organisation();
|
||||
o.setId(UUID.randomUUID());
|
||||
return o;
|
||||
}
|
||||
|
||||
private static ApproverAction approvedAction() {
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.setApproverId(UUID.randomUUID());
|
||||
a.setApproverName("Approver Test");
|
||||
a.setApproverRole("TRESORIER");
|
||||
a.setDecision("APPROVED");
|
||||
return a;
|
||||
}
|
||||
|
||||
private static ApproverAction pendingAction() {
|
||||
ApproverAction a = new ApproverAction();
|
||||
a.setApproverId(UUID.randomUUID());
|
||||
a.setApproverName("Approver Pending");
|
||||
a.setApproverRole("PRESIDENT");
|
||||
a.setDecision("PENDING");
|
||||
return a;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getRequiredApprovals
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("getRequiredApprovals: NONE renvoie 0")
|
||||
void getRequiredApprovals_none_returns0() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setRequiredLevel("NONE");
|
||||
assertThat(ta.getRequiredApprovals()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getRequiredApprovals: LEVEL1 renvoie 1")
|
||||
void getRequiredApprovals_level1_returns1() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setRequiredLevel("LEVEL1");
|
||||
assertThat(ta.getRequiredApprovals()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getRequiredApprovals: LEVEL2 renvoie 2")
|
||||
void getRequiredApprovals_level2_returns2() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setRequiredLevel("LEVEL2");
|
||||
assertThat(ta.getRequiredApprovals()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getRequiredApprovals: LEVEL3 renvoie 3")
|
||||
void getRequiredApprovals_level3_returns3() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setRequiredLevel("LEVEL3");
|
||||
assertThat(ta.getRequiredApprovals()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getRequiredApprovals: valeur inconnue renvoie 0 (default)")
|
||||
void getRequiredApprovals_default_returns0() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setRequiredLevel("INVALID");
|
||||
assertThat(ta.getRequiredApprovals()).isEqualTo(0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// countApprovals
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("countApprovals: aucun approbateur renvoie 0")
|
||||
void countApprovals_noApprovers_returns0() {
|
||||
TransactionApproval ta = TransactionApproval.builder()
|
||||
.transactionId(UUID.randomUUID())
|
||||
.transactionType("CONTRIBUTION")
|
||||
.amount(new BigDecimal("5000.00"))
|
||||
.requesterId(UUID.randomUUID())
|
||||
.requesterName("Test Requester")
|
||||
.requiredLevel("LEVEL1")
|
||||
.build();
|
||||
|
||||
assertThat(ta.countApprovals()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("countApprovals: seuls les APPROVED sont comptés")
|
||||
void countApprovals_mixedDecisions_countsApprovedOnly() {
|
||||
TransactionApproval ta = TransactionApproval.builder()
|
||||
.transactionId(UUID.randomUUID())
|
||||
.transactionType("DEPOSIT")
|
||||
.amount(new BigDecimal("10000.00"))
|
||||
.requesterId(UUID.randomUUID())
|
||||
.requesterName("Requester")
|
||||
.requiredLevel("LEVEL2")
|
||||
.build();
|
||||
|
||||
ta.addApproverAction(approvedAction());
|
||||
ta.addApproverAction(pendingAction());
|
||||
ta.addApproverAction(approvedAction());
|
||||
|
||||
assertThat(ta.countApprovals()).isEqualTo(2);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// hasAllApprovals
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("hasAllApprovals: NONE sans approbateur renvoie toujours true")
|
||||
void hasAllApprovals_none_alwaysTrue() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setRequiredLevel("NONE");
|
||||
assertThat(ta.hasAllApprovals()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasAllApprovals: LEVEL1 avec une approbation renvoie true")
|
||||
void hasAllApprovals_level1_oneApproval_true() {
|
||||
TransactionApproval ta = TransactionApproval.builder()
|
||||
.transactionId(UUID.randomUUID())
|
||||
.transactionType("CONTRIBUTION")
|
||||
.amount(new BigDecimal("1000.00"))
|
||||
.requesterId(UUID.randomUUID())
|
||||
.requesterName("Requester")
|
||||
.requiredLevel("LEVEL1")
|
||||
.build();
|
||||
|
||||
ta.addApproverAction(approvedAction());
|
||||
|
||||
assertThat(ta.hasAllApprovals()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasAllApprovals: LEVEL2 avec une seule approbation renvoie false")
|
||||
void hasAllApprovals_level2_onlyOneApproval_false() {
|
||||
TransactionApproval ta = TransactionApproval.builder()
|
||||
.transactionId(UUID.randomUUID())
|
||||
.transactionType("WITHDRAWAL")
|
||||
.amount(new BigDecimal("50000.00"))
|
||||
.requesterId(UUID.randomUUID())
|
||||
.requesterName("Requester")
|
||||
.requiredLevel("LEVEL2")
|
||||
.build();
|
||||
|
||||
ta.addApproverAction(approvedAction());
|
||||
|
||||
assertThat(ta.hasAllApprovals()).isFalse();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// addApproverAction
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("addApproverAction: ajoute l'action et positionne le parent")
|
||||
void addApproverAction_setsParentAndAdds() {
|
||||
TransactionApproval ta = TransactionApproval.builder()
|
||||
.transactionId(UUID.randomUUID())
|
||||
.transactionType("CONTRIBUTION")
|
||||
.amount(new BigDecimal("2000.00"))
|
||||
.requesterId(UUID.randomUUID())
|
||||
.requesterName("Requester")
|
||||
.requiredLevel("LEVEL1")
|
||||
.build();
|
||||
|
||||
ApproverAction action = pendingAction();
|
||||
ta.addApproverAction(action);
|
||||
|
||||
assertThat(ta.getApprovers()).hasSize(1);
|
||||
assertThat(action.getApproval()).isSameAs(ta);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isExpired
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("isExpired: expiresAt null renvoie false")
|
||||
void isExpired_expiresAtNull_returnsFalse() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setExpiresAt(null);
|
||||
assertThat(ta.isExpired()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isExpired: expiresAt dans le passé renvoie true")
|
||||
void isExpired_expiredInPast_returnsTrue() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setExpiresAt(LocalDateTime.now().minusMinutes(1));
|
||||
assertThat(ta.isExpired()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isExpired: expiresAt dans le futur renvoie false")
|
||||
void isExpired_futureExpiry_returnsFalse() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setExpiresAt(LocalDateTime.now().plusDays(1));
|
||||
assertThat(ta.isExpired()).isFalse();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isPending
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("isPending: statut PENDING renvoie true")
|
||||
void isPending_pending_returnsTrue() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setStatus("PENDING");
|
||||
assertThat(ta.isPending()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPending: statut APPROVED renvoie false")
|
||||
void isPending_approved_returnsFalse() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setStatus("APPROVED");
|
||||
assertThat(ta.isPending()).isFalse();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isCompleted
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("isCompleted: statut VALIDATED renvoie true")
|
||||
void isCompleted_validated_returnsTrue() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setStatus("VALIDATED");
|
||||
assertThat(ta.isCompleted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isCompleted: statut REJECTED renvoie true")
|
||||
void isCompleted_rejected_returnsTrue() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setStatus("REJECTED");
|
||||
assertThat(ta.isCompleted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isCompleted: statut CANCELLED renvoie true")
|
||||
void isCompleted_cancelled_returnsTrue() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setStatus("CANCELLED");
|
||||
assertThat(ta.isCompleted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isCompleted: statut PENDING renvoie false")
|
||||
void isCompleted_pending_returnsFalse() {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setStatus("PENDING");
|
||||
assertThat(ta.isCompleted()).isFalse();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// onCreate (réflexion)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise createdAt, currency, status, expiresAt si null")
|
||||
void onCreate_initializesNullFields() throws Exception {
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setCreatedAt(null);
|
||||
ta.setCurrency(null);
|
||||
ta.setStatus(null);
|
||||
ta.setExpiresAt(null);
|
||||
|
||||
Method onCreate = TransactionApproval.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(ta);
|
||||
|
||||
assertThat(ta.getCreatedAt()).isNotNull();
|
||||
assertThat(ta.getCurrency()).isEqualTo("XOF");
|
||||
assertThat(ta.getStatus()).isEqualTo("PENDING");
|
||||
assertThat(ta.getExpiresAt()).isNotNull();
|
||||
// expiresAt doit être ~7 jours après createdAt
|
||||
assertThat(ta.getExpiresAt()).isAfter(ta.getCreatedAt());
|
||||
assertThat(ta.getExpiresAt()).isBeforeOrEqualTo(ta.getCreatedAt().plusDays(7).plusSeconds(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: ne remplace pas createdAt si déjà renseigné")
|
||||
void onCreate_doesNotOverrideExistingCreatedAt() throws Exception {
|
||||
LocalDateTime existingDate = LocalDateTime.of(2025, 3, 10, 9, 0);
|
||||
LocalDateTime existingExpiry = LocalDateTime.of(2025, 3, 17, 9, 0);
|
||||
|
||||
TransactionApproval ta = new TransactionApproval();
|
||||
ta.setCreatedAt(existingDate);
|
||||
ta.setCurrency("EUR");
|
||||
ta.setStatus("APPROVED");
|
||||
ta.setExpiresAt(existingExpiry);
|
||||
|
||||
Method onCreate = TransactionApproval.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(ta);
|
||||
|
||||
assertThat(ta.getCreatedAt()).isEqualTo(existingDate);
|
||||
assertThat(ta.getCurrency()).isEqualTo("EUR");
|
||||
assertThat(ta.getStatus()).isEqualTo("APPROVED");
|
||||
assertThat(ta.getExpiresAt()).isEqualTo(existingExpiry);
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,22 @@ import dev.lions.unionflow.server.api.enums.wave.StatutCompteWave;
|
||||
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
|
||||
import dev.lions.unionflow.server.api.enums.wave.TypeTransactionWave;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("TransactionWave")
|
||||
@DisplayName("TransactionWave — couverture complète")
|
||||
class TransactionWaveTest {
|
||||
|
||||
// ─── Utilitaire ───────────────────────────────────────────────────────────
|
||||
|
||||
private static CompteWave newCompteWave() {
|
||||
CompteWave c = new CompteWave();
|
||||
c.setId(UUID.randomUUID());
|
||||
@@ -22,9 +28,7 @@ class TransactionWaveTest {
|
||||
return c;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getters/setters")
|
||||
void gettersSetters() {
|
||||
private static TransactionWave baseTransaction() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setWaveTransactionId("wave-123");
|
||||
t.setTypeTransaction(TypeTransactionWave.DEPOT);
|
||||
@@ -32,78 +36,545 @@ class TransactionWaveTest {
|
||||
t.setMontant(new BigDecimal("5000.00"));
|
||||
t.setCodeDevise("XOF");
|
||||
t.setCompteWave(newCompteWave());
|
||||
|
||||
assertThat(t.getWaveTransactionId()).isEqualTo("wave-123");
|
||||
assertThat(t.getTypeTransaction()).isEqualTo(TypeTransactionWave.DEPOT);
|
||||
assertThat(t.getStatutTransaction()).isEqualTo(StatutTransactionWave.REUSSIE);
|
||||
assertThat(t.getMontant()).isEqualByComparingTo("5000.00");
|
||||
return t;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isReussie")
|
||||
void isReussie() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setWaveTransactionId("x");
|
||||
t.setTypeTransaction(TypeTransactionWave.DEPOT);
|
||||
t.setMontant(BigDecimal.ONE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setCompteWave(newCompteWave());
|
||||
t.setStatutTransaction(StatutTransactionWave.REUSSIE);
|
||||
assertThat(t.isReussie()).isTrue();
|
||||
t.setStatutTransaction(StatutTransactionWave.ECHOUE);
|
||||
assertThat(t.isReussie()).isFalse();
|
||||
// ─── Getters / Setters ────────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Getters et setters")
|
||||
class GettersSetters {
|
||||
|
||||
@Test
|
||||
@DisplayName("Champs de base déjà couverts")
|
||||
void champsDeBase() {
|
||||
TransactionWave t = baseTransaction();
|
||||
|
||||
assertThat(t.getWaveTransactionId()).isEqualTo("wave-123");
|
||||
assertThat(t.getTypeTransaction()).isEqualTo(TypeTransactionWave.DEPOT);
|
||||
assertThat(t.getStatutTransaction()).isEqualTo(StatutTransactionWave.REUSSIE);
|
||||
assertThat(t.getMontant()).isEqualByComparingTo("5000.00");
|
||||
assertThat(t.getCodeDevise()).isEqualTo("XOF");
|
||||
assertThat(t.getCompteWave()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("waveRequestId et waveReference")
|
||||
void waveRequestIdEtReference() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setWaveRequestId("req-abc-456");
|
||||
t.setWaveReference("ref-xyz-789");
|
||||
|
||||
assertThat(t.getWaveRequestId()).isEqualTo("req-abc-456");
|
||||
assertThat(t.getWaveReference()).isEqualTo("ref-xyz-789");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("frais, montantNet")
|
||||
void fraisEtMontantNet() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setFrais(new BigDecimal("150.00"));
|
||||
t.setMontantNet(new BigDecimal("4850.00"));
|
||||
|
||||
assertThat(t.getFrais()).isEqualByComparingTo("150.00");
|
||||
assertThat(t.getMontantNet()).isEqualByComparingTo("4850.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("telephonePayeur et telephoneBeneficiaire")
|
||||
void telephones() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setTelephonePayeur("+22507111111");
|
||||
t.setTelephoneBeneficiaire("+22507222222");
|
||||
|
||||
assertThat(t.getTelephonePayeur()).isEqualTo("+22507111111");
|
||||
assertThat(t.getTelephoneBeneficiaire()).isEqualTo("+22507222222");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("metadonnees et reponseWaveApi")
|
||||
void metadonneesEtReponse() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setMetadonnees("{\"key\":\"val\"}");
|
||||
t.setReponseWaveApi("{\"status\":\"success\"}");
|
||||
|
||||
assertThat(t.getMetadonnees()).isEqualTo("{\"key\":\"val\"}");
|
||||
assertThat(t.getReponseWaveApi()).isEqualTo("{\"status\":\"success\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nombreTentatives, dateDerniereTentative, messageErreur")
|
||||
void tentativesEtErreur() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setNombreTentatives(3);
|
||||
t.setDateDerniereTentative(now);
|
||||
t.setMessageErreur("Délai dépassé");
|
||||
|
||||
assertThat(t.getNombreTentatives()).isEqualTo(3);
|
||||
assertThat(t.getDateDerniereTentative()).isEqualTo(now);
|
||||
assertThat(t.getMessageErreur()).isEqualTo("Délai dépassé");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("webhooks — liste modifiable")
|
||||
void webhooks() {
|
||||
TransactionWave t = baseTransaction();
|
||||
List<WebhookWave> webhooks = new ArrayList<>();
|
||||
WebhookWave wh = new WebhookWave();
|
||||
wh.setWaveEventId("evt-001");
|
||||
webhooks.add(wh);
|
||||
t.setWebhooks(webhooks);
|
||||
|
||||
assertThat(t.getWebhooks()).hasSize(1);
|
||||
assertThat(t.getWebhooks().get(0).getWaveEventId()).isEqualTo("evt-001");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Champs hérités de BaseEntity (id, dateCreation, creePar, modifiePar, version, actif)")
|
||||
void champsBaseEntity() {
|
||||
UUID id = UUID.randomUUID();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setId(id);
|
||||
t.setDateCreation(now);
|
||||
t.setDateModification(now);
|
||||
t.setCreePar("admin@test.com");
|
||||
t.setModifiePar("user@test.com");
|
||||
t.setVersion(1L);
|
||||
t.setActif(true);
|
||||
|
||||
assertThat(t.getId()).isEqualTo(id);
|
||||
assertThat(t.getDateCreation()).isEqualTo(now);
|
||||
assertThat(t.getDateModification()).isEqualTo(now);
|
||||
assertThat(t.getCreePar()).isEqualTo("admin@test.com");
|
||||
assertThat(t.getModifiePar()).isEqualTo("user@test.com");
|
||||
assertThat(t.getVersion()).isEqualTo(1L);
|
||||
assertThat(t.getActif()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetentee")
|
||||
void peutEtreRetentee() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setWaveTransactionId("x");
|
||||
t.setTypeTransaction(TypeTransactionWave.DEPOT);
|
||||
t.setMontant(BigDecimal.ONE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setCompteWave(newCompteWave());
|
||||
t.setStatutTransaction(StatutTransactionWave.ECHOUE);
|
||||
t.setNombreTentatives(2);
|
||||
assertThat(t.peutEtreRetentee()).isTrue();
|
||||
t.setNombreTentatives(5);
|
||||
assertThat(t.peutEtreRetentee()).isFalse();
|
||||
// ─── Builder ──────────────────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Builder Lombok")
|
||||
class BuilderTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Builder avec tous les champs obligatoires")
|
||||
void builderChampsObligatoires() {
|
||||
CompteWave compte = newCompteWave();
|
||||
TransactionWave t = TransactionWave.builder()
|
||||
.waveTransactionId("builder-wave-001")
|
||||
.typeTransaction(TypeTransactionWave.PAIEMENT)
|
||||
.statutTransaction(StatutTransactionWave.EN_ATTENTE)
|
||||
.montant(new BigDecimal("10000.00"))
|
||||
.codeDevise("XOF")
|
||||
.compteWave(compte)
|
||||
.build();
|
||||
|
||||
assertThat(t.getWaveTransactionId()).isEqualTo("builder-wave-001");
|
||||
assertThat(t.getTypeTransaction()).isEqualTo(TypeTransactionWave.PAIEMENT);
|
||||
assertThat(t.getStatutTransaction()).isEqualTo(StatutTransactionWave.EN_ATTENTE);
|
||||
assertThat(t.getMontant()).isEqualByComparingTo("10000.00");
|
||||
assertThat(t.getCodeDevise()).isEqualTo("XOF");
|
||||
assertThat(t.getCompteWave()).isEqualTo(compte);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Builder : valeurs @Builder.Default — statutTransaction=INITIALISE, nombreTentatives=0, webhooks vide")
|
||||
void builderDefaults() {
|
||||
CompteWave compte = newCompteWave();
|
||||
TransactionWave t = TransactionWave.builder()
|
||||
.waveTransactionId("builder-wave-defaults")
|
||||
.typeTransaction(TypeTransactionWave.RETRAIT)
|
||||
.montant(new BigDecimal("500.00"))
|
||||
.codeDevise("XOF")
|
||||
.compteWave(compte)
|
||||
.build();
|
||||
|
||||
assertThat(t.getStatutTransaction()).isEqualTo(StatutTransactionWave.INITIALISE);
|
||||
assertThat(t.getNombreTentatives()).isEqualTo(0);
|
||||
assertThat(t.getWebhooks()).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Builder avec tous les champs facultatifs")
|
||||
void builderChampsOptionnels() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
CompteWave compte = newCompteWave();
|
||||
TransactionWave t = TransactionWave.builder()
|
||||
.waveTransactionId("builder-wave-full")
|
||||
.waveRequestId("req-full-001")
|
||||
.waveReference("ref-full-001")
|
||||
.typeTransaction(TypeTransactionWave.TRANSFERT)
|
||||
.statutTransaction(StatutTransactionWave.REUSSIE)
|
||||
.montant(new BigDecimal("20000.00"))
|
||||
.frais(new BigDecimal("200.00"))
|
||||
.montantNet(new BigDecimal("19800.00"))
|
||||
.codeDevise("XOF")
|
||||
.telephonePayeur("+22507001001")
|
||||
.telephoneBeneficiaire("+22507002002")
|
||||
.metadonnees("{}")
|
||||
.reponseWaveApi("{\"code\":\"200\"}")
|
||||
.nombreTentatives(1)
|
||||
.dateDerniereTentative(now)
|
||||
.messageErreur(null)
|
||||
.compteWave(compte)
|
||||
.build();
|
||||
|
||||
assertThat(t.getWaveRequestId()).isEqualTo("req-full-001");
|
||||
assertThat(t.getWaveReference()).isEqualTo("ref-full-001");
|
||||
assertThat(t.getFrais()).isEqualByComparingTo("200.00");
|
||||
assertThat(t.getMontantNet()).isEqualByComparingTo("19800.00");
|
||||
assertThat(t.getTelephonePayeur()).isEqualTo("+22507001001");
|
||||
assertThat(t.getTelephoneBeneficiaire()).isEqualTo("+22507002002");
|
||||
assertThat(t.getMetadonnees()).isEqualTo("{}");
|
||||
assertThat(t.getReponseWaveApi()).isEqualTo("{\"code\":\"200\"}");
|
||||
assertThat(t.getNombreTentatives()).isEqualTo(1);
|
||||
assertThat(t.getDateDerniereTentative()).isEqualTo(now);
|
||||
assertThat(t.getMessageErreur()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("equals et hashCode")
|
||||
void equalsHashCode() {
|
||||
UUID id = UUID.randomUUID();
|
||||
CompteWave c = newCompteWave();
|
||||
TransactionWave a = new TransactionWave();
|
||||
a.setId(id);
|
||||
a.setWaveTransactionId("w1");
|
||||
a.setTypeTransaction(TypeTransactionWave.DEPOT);
|
||||
a.setStatutTransaction(StatutTransactionWave.REUSSIE);
|
||||
a.setMontant(BigDecimal.ONE);
|
||||
a.setCodeDevise("XOF");
|
||||
a.setCompteWave(c);
|
||||
TransactionWave b = new TransactionWave();
|
||||
b.setId(id);
|
||||
b.setWaveTransactionId("w1");
|
||||
b.setTypeTransaction(TypeTransactionWave.DEPOT);
|
||||
b.setStatutTransaction(StatutTransactionWave.REUSSIE);
|
||||
b.setMontant(BigDecimal.ONE);
|
||||
b.setCodeDevise("XOF");
|
||||
b.setCompteWave(c);
|
||||
assertThat(a).isEqualTo(b);
|
||||
assertThat(a.hashCode()).isEqualTo(b.hashCode());
|
||||
// ─── Méthodes métier ──────────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("Méthodes métier")
|
||||
class MethodesMetier {
|
||||
|
||||
@Test
|
||||
@DisplayName("isReussie() retourne true si statut=REUSSIE")
|
||||
void isReussie_statutReussie_retourneTrue() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.REUSSIE);
|
||||
assertThat(t.isReussie()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isReussie() retourne false si statut=ECHOUE")
|
||||
void isReussie_statutEchoue_retourneFalse() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.ECHOUE);
|
||||
assertThat(t.isReussie()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isReussie() retourne false si statut=INITIALISE")
|
||||
void isReussie_statutInitialise_retourneFalse() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
assertThat(t.isReussie()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetentee() retourne true si statut=ECHOUE et tentatives<5")
|
||||
void peutEtreRetentee_echoue_tentativesFaibles_retourneTrue() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.ECHOUE);
|
||||
t.setNombreTentatives(2);
|
||||
assertThat(t.peutEtreRetentee()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetentee() retourne true si statut=EXPIRED et tentatives<5")
|
||||
void peutEtreRetentee_expired_tentativesFaibles_retourneTrue() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.EXPIRED);
|
||||
t.setNombreTentatives(0);
|
||||
assertThat(t.peutEtreRetentee()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetentee() retourne false si tentatives=5")
|
||||
void peutEtreRetentee_tentativesMax_retourneFalse() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.ECHOUE);
|
||||
t.setNombreTentatives(5);
|
||||
assertThat(t.peutEtreRetentee()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetentee() retourne false si statut=REUSSIE")
|
||||
void peutEtreRetentee_statutReussie_retourneFalse() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.REUSSIE);
|
||||
t.setNombreTentatives(1);
|
||||
assertThat(t.peutEtreRetentee()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetentee() retourne true si nombreTentatives=null")
|
||||
void peutEtreRetentee_tentativesNull_retourneTrue() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.ECHOUE);
|
||||
t.setNombreTentatives(null);
|
||||
assertThat(t.peutEtreRetentee()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetentee() retourne false si statut=INITIALISE")
|
||||
void peutEtreRetentee_statutInitialise_retourneFalse() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setNombreTentatives(1);
|
||||
assertThat(t.peutEtreRetentee()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetentee() retourne false si statut=ANNULEE")
|
||||
void peutEtreRetentee_statutAnnulee_retourneFalse() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setStatutTransaction(StatutTransactionWave.ANNULEE);
|
||||
t.setNombreTentatives(0);
|
||||
assertThat(t.peutEtreRetentee()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toString non null")
|
||||
void toString_nonNull() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setWaveTransactionId("x");
|
||||
t.setTypeTransaction(TypeTransactionWave.DEPOT);
|
||||
t.setMontant(BigDecimal.ONE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setCompteWave(newCompteWave());
|
||||
assertThat(t.toString()).isNotNull().isNotEmpty();
|
||||
// ─── @PrePersist onCreate ─────────────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("@PrePersist onCreate()")
|
||||
class OnCreate {
|
||||
|
||||
@Test
|
||||
@DisplayName("statutTransaction=null est initialisé à INITIALISE")
|
||||
void statutTransactionNull_initialiseeAInitialise() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(null);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(new BigDecimal("1000.00"));
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getStatutTransaction()).isEqualTo(StatutTransactionWave.INITIALISE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("statutTransaction déjà défini n'est pas écrasé")
|
||||
void statutTransactionDejaDefini_nonEcrase() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.EN_COURS);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(new BigDecimal("1000.00"));
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getStatutTransaction()).isEqualTo(StatutTransactionWave.EN_COURS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("codeDevise=null est initialisé à XOF")
|
||||
void codeDeviseNull_initialiseeAXof() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise(null);
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(new BigDecimal("1000.00"));
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getCodeDevise()).isEqualTo("XOF");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("codeDevise vide ('') est initialisé à XOF")
|
||||
void codeDeviseVide_initialiseeAXof() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise("");
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(new BigDecimal("1000.00"));
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getCodeDevise()).isEqualTo("XOF");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("codeDevise déjà défini n'est pas écrasé")
|
||||
void codeDeviseDejaDefini_nonEcrase() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise("EUR");
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(new BigDecimal("1000.00"));
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getCodeDevise()).isEqualTo("EUR");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nombreTentatives=null est initialisé à 0")
|
||||
void nombreTentativesNull_initialiseeAZero() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setNombreTentatives(null);
|
||||
t.setMontant(new BigDecimal("1000.00"));
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getNombreTentatives()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nombreTentatives déjà défini n'est pas écrasé")
|
||||
void nombreTentativesDejaDefini_nonEcrase() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setNombreTentatives(3);
|
||||
t.setMontant(new BigDecimal("1000.00"));
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getNombreTentatives()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("montantNet=null est calculé quand montant et frais sont définis")
|
||||
void montantNetNull_calculeDepuisMontantEtFrais() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(new BigDecimal("5000.00"));
|
||||
t.setFrais(new BigDecimal("150.00"));
|
||||
t.setMontantNet(null);
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getMontantNet()).isEqualByComparingTo("4850.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("montantNet=null n'est pas calculé si frais=null")
|
||||
void montantNetNull_nonCalculeSiFraisNull() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(new BigDecimal("5000.00"));
|
||||
t.setFrais(null);
|
||||
t.setMontantNet(null);
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getMontantNet()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("montantNet=null n'est pas calculé si montant=null")
|
||||
void montantNetNull_nonCalculeSiMontantNull() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(null);
|
||||
t.setFrais(new BigDecimal("150.00"));
|
||||
t.setMontantNet(null);
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getMontantNet()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("montantNet déjà défini n'est pas recalculé")
|
||||
void montantNetDejaDefini_nonRecalcule() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(new BigDecimal("5000.00"));
|
||||
t.setFrais(new BigDecimal("150.00"));
|
||||
t.setMontantNet(new BigDecimal("9999.00"));
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getMontantNet()).isEqualByComparingTo("9999.00");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate() initialise aussi dateCreation et actif via BaseEntity")
|
||||
void onCreateInitialiseBaseEntity() {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setStatutTransaction(StatutTransactionWave.INITIALISE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setNombreTentatives(0);
|
||||
t.setMontant(new BigDecimal("1000.00"));
|
||||
t.setDateCreation(null);
|
||||
t.setActif(null);
|
||||
t.onCreate();
|
||||
|
||||
assertThat(t.getDateCreation()).isNotNull();
|
||||
assertThat(t.getActif()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Equals / HashCode / toString ────────────────────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("equals, hashCode et toString")
|
||||
class EgaliteEtToString {
|
||||
|
||||
@Test
|
||||
@DisplayName("Deux instances avec le même id sont égales")
|
||||
void equals_memeId_egales() {
|
||||
UUID id = UUID.randomUUID();
|
||||
CompteWave c = newCompteWave();
|
||||
|
||||
TransactionWave a = buildTransaction(id, "w1", c);
|
||||
TransactionWave b = buildTransaction(id, "w1", c);
|
||||
|
||||
assertThat(a).isEqualTo(b);
|
||||
assertThat(a.hashCode()).isEqualTo(b.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deux instances avec des id différents ne sont pas égales")
|
||||
void equals_idsDifferents_pasEgales() {
|
||||
TransactionWave a = buildTransaction(UUID.randomUUID(), "w1", newCompteWave());
|
||||
TransactionWave b = buildTransaction(UUID.randomUUID(), "w2", newCompteWave());
|
||||
|
||||
assertThat(a).isNotEqualTo(b);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toString() n'est pas null ni vide")
|
||||
void toString_nonNullNonVide() {
|
||||
TransactionWave t = baseTransaction();
|
||||
assertThat(t.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toString() contient le waveTransactionId")
|
||||
void toString_contientWaveTransactionId() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.setWaveTransactionId("wave-toString-test");
|
||||
assertThat(t.toString()).contains("wave-toString-test");
|
||||
}
|
||||
|
||||
private TransactionWave buildTransaction(UUID id, String waveId, CompteWave compte) {
|
||||
TransactionWave t = new TransactionWave();
|
||||
t.setId(id);
|
||||
t.setWaveTransactionId(waveId);
|
||||
t.setTypeTransaction(TypeTransactionWave.DEPOT);
|
||||
t.setStatutTransaction(StatutTransactionWave.REUSSIE);
|
||||
t.setMontant(BigDecimal.ONE);
|
||||
t.setCodeDevise("XOF");
|
||||
t.setCompteWave(compte);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── marquerCommeModifie (hérité de BaseEntity) ──────────────────────────
|
||||
|
||||
@Nested
|
||||
@DisplayName("marquerCommeModifie() — hérité de BaseEntity")
|
||||
class MarquerCommeModifie {
|
||||
|
||||
@Test
|
||||
@DisplayName("marquerCommeModifie() met à jour dateModification et modifiePar")
|
||||
void marquerCommeModifie_metAJourChamps() {
|
||||
TransactionWave t = baseTransaction();
|
||||
t.marquerCommeModifie("agent@unionflow.dev");
|
||||
|
||||
assertThat(t.getDateModification()).isNotNull();
|
||||
assertThat(t.getModifiePar()).isEqualTo("agent@unionflow.dev");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package dev.lions.unionflow.server.entity;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -29,6 +30,34 @@ class TypeReferenceTest {
|
||||
assertThat(tr.getEstDefaut()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: domaine et code non-null → normalisés en majuscules")
|
||||
void onCreate_normalisesDomaineAndCode() throws Exception {
|
||||
TypeReference tr = new TypeReference();
|
||||
tr.setDomaine("statut_org");
|
||||
tr.setCode("active");
|
||||
tr.setLibelle("Actif");
|
||||
Method onCreate = TypeReference.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(tr);
|
||||
assertThat(tr.getDomaine()).isEqualTo("STATUT_ORG");
|
||||
assertThat(tr.getCode()).isEqualTo("ACTIVE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: domaine null → skipped (no NPE), code null → skipped")
|
||||
void onCreate_nullDomaineAndCode_skipped() throws Exception {
|
||||
TypeReference tr = new TypeReference();
|
||||
tr.setDomaine(null);
|
||||
tr.setCode(null);
|
||||
tr.setLibelle("L");
|
||||
Method onCreate = TypeReference.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(tr);
|
||||
assertThat(tr.getDomaine()).isNull();
|
||||
assertThat(tr.getCode()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("equals et hashCode")
|
||||
void equalsHashCode() {
|
||||
|
||||
@@ -6,6 +6,8 @@ import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -83,4 +85,69 @@ class ValidationEtapeDemandeTest {
|
||||
v.setStatut(StatutValidationEtape.EN_ATTENTE);
|
||||
assertThat(v.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estFinalisee: true pour REJETEE")
|
||||
void estFinalisee_rejetee() {
|
||||
ValidationEtapeDemande v = new ValidationEtapeDemande();
|
||||
v.setDemandeAide(newDemandeAide());
|
||||
v.setEtapeNumero(1);
|
||||
v.setStatut(StatutValidationEtape.REJETEE);
|
||||
assertThat(v.estFinalisee()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estFinalisee: true pour DELEGUEE")
|
||||
void estFinalisee_deleguee() {
|
||||
ValidationEtapeDemande v = new ValidationEtapeDemande();
|
||||
v.setDemandeAide(newDemandeAide());
|
||||
v.setEtapeNumero(1);
|
||||
v.setStatut(StatutValidationEtape.DELEGUEE);
|
||||
assertThat(v.estFinalisee()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estFinalisee: true pour EXPIREE")
|
||||
void estFinalisee_expiree() {
|
||||
ValidationEtapeDemande v = new ValidationEtapeDemande();
|
||||
v.setDemandeAide(newDemandeAide());
|
||||
v.setEtapeNumero(1);
|
||||
v.setStatut(StatutValidationEtape.EXPIREE);
|
||||
assertThat(v.estFinalisee()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate (PrePersist) - statut null est initialisé à EN_ATTENTE via réflexion")
|
||||
void onCreate_withNullStatut_setsEnAttente() throws Exception {
|
||||
ValidationEtapeDemande v = new ValidationEtapeDemande();
|
||||
v.setDemandeAide(newDemandeAide());
|
||||
v.setEtapeNumero(1);
|
||||
|
||||
// Forcer statut à null via le champ Lombok
|
||||
Field statutField = ValidationEtapeDemande.class.getDeclaredField("statut");
|
||||
statutField.setAccessible(true);
|
||||
statutField.set(v, null);
|
||||
assertThat(v.getStatut()).isNull();
|
||||
|
||||
Method onCreate = ValidationEtapeDemande.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(v);
|
||||
|
||||
assertThat(v.getStatut()).isEqualTo(StatutValidationEtape.EN_ATTENTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate (PrePersist) - statut déjà défini n'est pas écrasé")
|
||||
void onCreate_withExistingStatut_keepsStatut() throws Exception {
|
||||
ValidationEtapeDemande v = new ValidationEtapeDemande();
|
||||
v.setDemandeAide(newDemandeAide());
|
||||
v.setEtapeNumero(1);
|
||||
v.setStatut(StatutValidationEtape.APPROUVEE);
|
||||
|
||||
Method onCreate = ValidationEtapeDemande.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(v);
|
||||
|
||||
assertThat(v.getStatut()).isEqualTo(StatutValidationEtape.APPROUVEE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import dev.lions.unionflow.server.api.enums.wave.StatutWebhook;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -51,6 +52,16 @@ class WebhookWaveTest {
|
||||
assertThat(w.peutEtreRetente()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetente: false si statut TRAITE (ni ECHOUE ni EN_ATTENTE)")
|
||||
void peutEtreRetente_false_whenTraite() {
|
||||
WebhookWave w = new WebhookWave();
|
||||
w.setWaveEventId("x");
|
||||
w.setStatutTraitement(StatutWebhook.TRAITE.name());
|
||||
w.setNombreTentatives(1);
|
||||
assertThat(w.peutEtreRetente()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("equals et hashCode")
|
||||
void equalsHashCode() {
|
||||
@@ -75,4 +86,32 @@ class WebhookWaveTest {
|
||||
w.setStatutTraitement(StatutWebhook.EN_ATTENTE.name());
|
||||
assertThat(w.toString()).isNotNull().isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("peutEtreRetente: true si nombreTentatives null et EN_ATTENTE")
|
||||
void peutEtreRetente_nombreTentativesNull_returnsTrue() {
|
||||
WebhookWave w = new WebhookWave();
|
||||
w.setWaveEventId("x");
|
||||
w.setStatutTraitement(StatutWebhook.EN_ATTENTE.name());
|
||||
w.setNombreTentatives(null);
|
||||
assertThat(w.peutEtreRetente()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("onCreate: initialise les défauts si null")
|
||||
void onCreate_setsDefaults() throws Exception {
|
||||
WebhookWave w = new WebhookWave();
|
||||
w.setWaveEventId("evt-onc");
|
||||
w.setStatutTraitement(null);
|
||||
w.setDateReception(null);
|
||||
w.setNombreTentatives(null);
|
||||
|
||||
Method onCreate = WebhookWave.class.getDeclaredMethod("onCreate");
|
||||
onCreate.setAccessible(true);
|
||||
onCreate.invoke(w);
|
||||
|
||||
assertThat(w.getStatutTraitement()).isEqualTo(StatutWebhook.EN_ATTENTE.name());
|
||||
assertThat(w.getDateReception()).isNotNull();
|
||||
assertThat(w.getNombreTentatives()).isEqualTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,16 +48,19 @@ class CampagneCollecteTest {
|
||||
void equalsHashCode() {
|
||||
UUID id = UUID.randomUUID();
|
||||
Organisation o = newOrganisation();
|
||||
LocalDateTime dateOuverture = LocalDateTime.of(2026, 1, 1, 0, 0, 0);
|
||||
CampagneCollecte a = new CampagneCollecte();
|
||||
a.setId(id);
|
||||
a.setOrganisation(o);
|
||||
a.setTitre("T");
|
||||
a.setStatut(StatutCampagneCollecte.BROUILLON);
|
||||
a.setDateOuverture(dateOuverture);
|
||||
CampagneCollecte b = new CampagneCollecte();
|
||||
b.setId(id);
|
||||
b.setOrganisation(o);
|
||||
b.setTitre("T");
|
||||
b.setStatut(StatutCampagneCollecte.BROUILLON);
|
||||
b.setDateOuverture(dateOuverture);
|
||||
assertThat(a).isEqualTo(b);
|
||||
assertThat(a.hashCode()).isEqualTo(b.hashCode());
|
||||
}
|
||||
|
||||
@@ -2,10 +2,16 @@ package dev.lions.unionflow.server.entity.listener;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Adresse;
|
||||
import dev.lions.unionflow.server.entity.BaseEntity;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
import io.quarkus.arc.Arc;
|
||||
import io.quarkus.arc.ArcContainer;
|
||||
import io.quarkus.arc.InstanceHandle;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour AuditEntityListener (avantCreation, avantModification, toutes les branches).
|
||||
@@ -80,4 +86,129 @@ class AuditEntityListenerTest {
|
||||
|
||||
assertThat(entity.getModifiePar()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@DisplayName("avantCreation: retourne l'email quand KeycloakService authentifié et email non vide")
|
||||
void avantCreation_authenticated_withEmail_setsEmail() {
|
||||
ArcContainer mockContainer = mock(ArcContainer.class);
|
||||
InstanceHandle<KeycloakService> mockHandle = mock(InstanceHandle.class);
|
||||
KeycloakService mockService = mock(KeycloakService.class);
|
||||
|
||||
when(mockContainer.instance(KeycloakService.class)).thenReturn(mockHandle);
|
||||
when(mockHandle.get()).thenReturn(mockService);
|
||||
when(mockService.isAuthenticated()).thenReturn(true);
|
||||
when(mockService.getCurrentUserEmail()).thenReturn("user@example.com");
|
||||
|
||||
try (MockedStatic<Arc> arcMock = mockStatic(Arc.class)) {
|
||||
arcMock.when(Arc::container).thenReturn(mockContainer);
|
||||
|
||||
BaseEntity entity = new Adresse();
|
||||
entity.setCreePar(null);
|
||||
new AuditEntityListener().avantCreation(entity);
|
||||
|
||||
assertThat(entity.getCreePar()).isEqualTo("user@example.com");
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Branches manquantes dans obtenirUtilisateurCourant()
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@DisplayName("obtenirUtilisateurCourant: keycloakService null → retourne 'system' (L92 branche keycloakService==null)")
|
||||
void avantCreation_keycloakServiceNull_returnsSystem() {
|
||||
ArcContainer mockContainer = mock(ArcContainer.class);
|
||||
InstanceHandle<KeycloakService> mockHandle = mock(InstanceHandle.class);
|
||||
|
||||
when(mockContainer.instance(KeycloakService.class)).thenReturn(mockHandle);
|
||||
// get() retourne null → L92 condition (keycloakService != null) = false → retourne "system"
|
||||
when(mockHandle.get()).thenReturn(null);
|
||||
|
||||
try (MockedStatic<Arc> arcMock = mockStatic(Arc.class)) {
|
||||
arcMock.when(Arc::container).thenReturn(mockContainer);
|
||||
|
||||
BaseEntity entity = new Adresse();
|
||||
entity.setCreePar(null);
|
||||
new AuditEntityListener().avantCreation(entity);
|
||||
|
||||
// keycloakService null → fallback "system"
|
||||
assertThat(entity.getCreePar()).isEqualTo("system");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@DisplayName("obtenirUtilisateurCourant: authentifié mais email blank → retourne 'system' (L95 branche email.isBlank)")
|
||||
void avantCreation_authenticated_blankEmail_returnsSystem() {
|
||||
ArcContainer mockContainer = mock(ArcContainer.class);
|
||||
InstanceHandle<KeycloakService> mockHandle = mock(InstanceHandle.class);
|
||||
KeycloakService mockService = mock(KeycloakService.class);
|
||||
|
||||
when(mockContainer.instance(KeycloakService.class)).thenReturn(mockHandle);
|
||||
when(mockHandle.get()).thenReturn(mockService);
|
||||
when(mockService.isAuthenticated()).thenReturn(true);
|
||||
// email blank → L95 condition !email.isBlank() = false → ne retourne pas email → tombe sur "system"
|
||||
when(mockService.getCurrentUserEmail()).thenReturn(" ");
|
||||
|
||||
try (MockedStatic<Arc> arcMock = mockStatic(Arc.class)) {
|
||||
arcMock.when(Arc::container).thenReturn(mockContainer);
|
||||
|
||||
BaseEntity entity = new Adresse();
|
||||
entity.setCreePar(null);
|
||||
new AuditEntityListener().avantCreation(entity);
|
||||
|
||||
assertThat(entity.getCreePar()).isEqualTo("system");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@DisplayName("obtenirUtilisateurCourant: authentifié mais email null → retourne 'system' (L95 branche email==null)")
|
||||
void avantCreation_authenticated_nullEmail_returnsSystem() {
|
||||
ArcContainer mockContainer = mock(ArcContainer.class);
|
||||
InstanceHandle<KeycloakService> mockHandle = mock(InstanceHandle.class);
|
||||
KeycloakService mockService = mock(KeycloakService.class);
|
||||
|
||||
when(mockContainer.instance(KeycloakService.class)).thenReturn(mockHandle);
|
||||
when(mockHandle.get()).thenReturn(mockService);
|
||||
when(mockService.isAuthenticated()).thenReturn(true);
|
||||
// email null → L95 condition (email != null && !email.isBlank()) = false → retourne "system"
|
||||
when(mockService.getCurrentUserEmail()).thenReturn(null);
|
||||
|
||||
try (MockedStatic<Arc> arcMock = mockStatic(Arc.class)) {
|
||||
arcMock.when(Arc::container).thenReturn(mockContainer);
|
||||
|
||||
BaseEntity entity = new Adresse();
|
||||
entity.setCreePar(null);
|
||||
new AuditEntityListener().avantCreation(entity);
|
||||
|
||||
assertThat(entity.getCreePar()).isEqualTo("system");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
@DisplayName("obtenirUtilisateurCourant: non authentifié → retourne 'system' (L93 branche isAuthenticated=false)")
|
||||
void avantCreation_notAuthenticated_returnsSystem() {
|
||||
ArcContainer mockContainer = mock(ArcContainer.class);
|
||||
InstanceHandle<KeycloakService> mockHandle = mock(InstanceHandle.class);
|
||||
KeycloakService mockService = mock(KeycloakService.class);
|
||||
|
||||
when(mockContainer.instance(KeycloakService.class)).thenReturn(mockHandle);
|
||||
when(mockHandle.get()).thenReturn(mockService);
|
||||
// isAuthenticated=false → L93 condition false → ne rentre pas dans le if → retourne "system"
|
||||
when(mockService.isAuthenticated()).thenReturn(false);
|
||||
|
||||
try (MockedStatic<Arc> arcMock = mockStatic(Arc.class)) {
|
||||
arcMock.when(Arc::container).thenReturn(mockContainer);
|
||||
|
||||
BaseEntity entity = new Adresse();
|
||||
entity.setCreePar(null);
|
||||
new AuditEntityListener().avantCreation(entity);
|
||||
|
||||
assertThat(entity.getCreePar()).isEqualTo("system");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ class BusinessExceptionMapperTest {
|
||||
.post("/api/membres/search/advanced")
|
||||
.then()
|
||||
.statusCode(400)
|
||||
.body("error", equalTo("Requête invalide"))
|
||||
.body("message", notNullValue());
|
||||
.body("error", notNullValue())
|
||||
.body("status", equalTo(400));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -38,8 +38,8 @@ class BusinessExceptionMapperTest {
|
||||
.get("/api/membres/{id}")
|
||||
.then()
|
||||
.statusCode(404)
|
||||
.body("error", equalTo("Non trouvé"))
|
||||
.body("message", notNullValue());
|
||||
.body("error", notNullValue())
|
||||
.body("status", equalTo(404));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -52,7 +52,8 @@ class BusinessExceptionMapperTest {
|
||||
.get("/api/evenements/{id}")
|
||||
.then()
|
||||
.statusCode(404)
|
||||
.body("error", equalTo("Non trouvé"));
|
||||
.body("error", notNullValue())
|
||||
.body("status", equalTo(404));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -65,6 +66,7 @@ class BusinessExceptionMapperTest {
|
||||
.get("/api/organisations/{id}")
|
||||
.then()
|
||||
.statusCode(404)
|
||||
.body("error", equalTo("Non trouvé"));
|
||||
.body("error", notNullValue())
|
||||
.body("status", equalTo(404));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package dev.lions.unionflow.server.exception;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@@ -12,6 +14,7 @@ import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -147,6 +150,64 @@ class GlobalExceptionMapperTest {
|
||||
java.util.Map<String, Object> body = (java.util.Map<String, Object>) r.getEntity();
|
||||
assertThat(body.get("error")).isEqualTo("An error occurred");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Exception avec getMessage() null → utilise getClass().getSimpleName() pour le message logué (missed branch L53)")
|
||||
void toResponse_exceptionWithNullMessage_usesClassSimpleName() {
|
||||
// RuntimeException construit sans message → getMessage() = null
|
||||
// → branche false de (exception.getMessage() != null ? ... : exception.getClass().getSimpleName())
|
||||
// Note: statusCode=500 → buildErrorResponse retourne "Internal server error" (branch >=500)
|
||||
// Mais le message logué utilise getSimpleName() — couvre la branche L53 false
|
||||
RuntimeException ex = new RuntimeException((String) null);
|
||||
assertThat(ex.getMessage()).isNull(); // sanity check
|
||||
|
||||
Response r = globalExceptionMapper.toResponse(ex);
|
||||
assertThat(r.getStatus()).isEqualTo(500);
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, Object> body = (java.util.Map<String, Object>) r.getEntity();
|
||||
// statusCode >= 500 → "Internal server error"
|
||||
assertThat(body.get("error")).isEqualTo("Internal server error");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toResponse avec uriInfo non-null → endpoint récupéré via getPath() (branche uriInfo!=null L45)")
|
||||
void toResponse_withNonNullUriInfo_usesUriInfoPath() throws Exception {
|
||||
// Injecter un mock UriInfo via réflexion pour couvrir la branche uriInfo != null (L45)
|
||||
UriInfo mockUriInfo = mock(UriInfo.class);
|
||||
when(mockUriInfo.getPath()).thenReturn("/api/test/path");
|
||||
|
||||
java.lang.reflect.Field uriInfoField = GlobalExceptionMapper.class.getDeclaredField("uriInfo");
|
||||
uriInfoField.setAccessible(true);
|
||||
uriInfoField.set(globalExceptionMapper, mockUriInfo);
|
||||
|
||||
try {
|
||||
Response r = globalExceptionMapper.toResponse(new RuntimeException("with uri"));
|
||||
assertThat(r.getStatus()).isEqualTo(500);
|
||||
} finally {
|
||||
// Remettre à null pour ne pas affecter les autres tests
|
||||
uriInfoField.set(globalExceptionMapper, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toResponse avec uriInfo non-null mais getPath() lève exception → catch block couvert (L48)")
|
||||
void toResponse_uriInfoGetPathThrows_catchBlockCovered() throws Exception {
|
||||
// Simule le cas où uriInfo est non-null mais getPath() échoue (ex: pas de contexte request actif)
|
||||
UriInfo mockUriInfo = mock(UriInfo.class);
|
||||
when(mockUriInfo.getPath()).thenThrow(new RuntimeException("ContextNotActive simulé"));
|
||||
|
||||
java.lang.reflect.Field uriInfoField = GlobalExceptionMapper.class.getDeclaredField("uriInfo");
|
||||
uriInfoField.setAccessible(true);
|
||||
uriInfoField.set(globalExceptionMapper, mockUriInfo);
|
||||
|
||||
try {
|
||||
Response r = globalExceptionMapper.toResponse(new IllegalArgumentException("test catch block"));
|
||||
// La réponse est construite normalement, l'exception getPath() est ignorée → endpoint reste "unknown"
|
||||
assertThat(r.getStatus()).isEqualTo(400);
|
||||
} finally {
|
||||
uriInfoField.set(globalExceptionMapper, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@@ -231,4 +292,84 @@ class GlobalExceptionMapperTest {
|
||||
assertThat(body.get("error")).isEqualTo("access denied");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("determineSource - branche par nom de classe d'exception")
|
||||
class DetermineSourceBranches {
|
||||
|
||||
/** Exception dont le nom de classe simple contient "Database". */
|
||||
private static final class DatabaseException extends RuntimeException {
|
||||
DatabaseException() { super("db error"); }
|
||||
}
|
||||
|
||||
/** Exception dont le nom de classe simple contient "SQL". */
|
||||
private static final class SQLQueryException extends RuntimeException {
|
||||
SQLQueryException() { super("sql error"); }
|
||||
}
|
||||
|
||||
/** Exception dont le nom de classe simple contient "Persistence". */
|
||||
private static final class PersistenceException extends RuntimeException {
|
||||
PersistenceException() { super("persistence error"); }
|
||||
}
|
||||
|
||||
/** Exception dont le nom de classe simple contient "Auth". */
|
||||
private static final class AuthException extends RuntimeException {
|
||||
AuthException() { super("auth error"); }
|
||||
}
|
||||
|
||||
/** Exception dont le nom de classe simple contient "Validation". */
|
||||
private static final class ValidationException extends RuntimeException {
|
||||
ValidationException() { super("validation error"); }
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Exception nommée 'DatabaseException' → retourne 500 (source=Database)")
|
||||
void determineSource_databaseException_returns500() {
|
||||
Response r = globalExceptionMapper.toResponse(new DatabaseException());
|
||||
assertThat(r.getStatus()).isEqualTo(500);
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, Object> body = (java.util.Map<String, Object>) r.getEntity();
|
||||
assertThat(body.get("error")).isEqualTo("Internal server error");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Exception nommée 'SQLQueryException' → retourne 500 (source=Database)")
|
||||
void determineSource_sqlException_returns500() {
|
||||
Response r = globalExceptionMapper.toResponse(new SQLQueryException());
|
||||
assertThat(r.getStatus()).isEqualTo(500);
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, Object> body = (java.util.Map<String, Object>) r.getEntity();
|
||||
assertThat(body.get("error")).isEqualTo("Internal server error");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Exception nommée 'PersistenceException' → retourne 500 (source=Database)")
|
||||
void determineSource_persistenceException_returns500() {
|
||||
Response r = globalExceptionMapper.toResponse(new PersistenceException());
|
||||
assertThat(r.getStatus()).isEqualTo(500);
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, Object> body = (java.util.Map<String, Object>) r.getEntity();
|
||||
assertThat(body.get("error")).isEqualTo("Internal server error");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Exception nommée 'AuthException' → retourne 500 (source=Auth)")
|
||||
void determineSource_authException_returns500() {
|
||||
Response r = globalExceptionMapper.toResponse(new AuthException());
|
||||
assertThat(r.getStatus()).isEqualTo(500);
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, Object> body = (java.util.Map<String, Object>) r.getEntity();
|
||||
assertThat(body.get("error")).isEqualTo("Internal server error");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Exception nommée 'ValidationException' → retourne 500 (source=Validation)")
|
||||
void determineSource_validationException_returns500() {
|
||||
Response r = globalExceptionMapper.toResponse(new ValidationException());
|
||||
assertThat(r.getStatus()).isEqualTo(500);
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, Object> body = (java.util.Map<String, Object>) r.getEntity();
|
||||
assertThat(body.get("error")).isEqualTo("Internal server error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package dev.lions.unionflow.server.exception;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.security.TestSecurity;
|
||||
import io.restassured.http.ContentType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -14,7 +18,7 @@ class JsonProcessingExceptionMapperTest {
|
||||
|
||||
@Test
|
||||
@TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
|
||||
@DisplayName("JSON invalide (body mal formé) → 400 + message")
|
||||
@DisplayName("JSON invalide (body mal formé) → 400")
|
||||
void toResponse_invalidJson_returns400() {
|
||||
given()
|
||||
.contentType(ContentType.JSON)
|
||||
@@ -22,9 +26,7 @@ class JsonProcessingExceptionMapperTest {
|
||||
.when()
|
||||
.post("/api/evenements")
|
||||
.then()
|
||||
.statusCode(400)
|
||||
.body("message", notNullValue())
|
||||
.body("details", notNullValue());
|
||||
.statusCode(400);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -37,8 +39,7 @@ class JsonProcessingExceptionMapperTest {
|
||||
.when()
|
||||
.post("/api/evenements")
|
||||
.then()
|
||||
.statusCode(400)
|
||||
.body("message", anyOf(containsString("JSON"), containsString("format")));
|
||||
.statusCode(400);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -51,7 +52,28 @@ class JsonProcessingExceptionMapperTest {
|
||||
.when()
|
||||
.post("/api/evenements")
|
||||
.then()
|
||||
.statusCode(400)
|
||||
.body("message", notNullValue());
|
||||
.statusCode(400);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toResponse: originalMessage non null → utilise originalMessage")
|
||||
void toResponse_withOriginalMessage_usesOriginalMessage() {
|
||||
JsonProcessingExceptionMapper mapper = new JsonProcessingExceptionMapper();
|
||||
JsonProcessingException ex = mock(JsonProcessingException.class);
|
||||
when(ex.getMessage()).thenReturn("full message");
|
||||
when(ex.getOriginalMessage()).thenReturn("original message");
|
||||
Response response = mapper.toResponse(ex);
|
||||
assertThat(response.getStatus()).isEqualTo(400);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toResponse: originalMessage null → utilise getMessage")
|
||||
void toResponse_nullOriginalMessage_usesGetMessage() {
|
||||
JsonProcessingExceptionMapper mapper = new JsonProcessingExceptionMapper();
|
||||
JsonProcessingException ex = mock(JsonProcessingException.class);
|
||||
when(ex.getMessage()).thenReturn("full message");
|
||||
when(ex.getOriginalMessage()).thenReturn(null);
|
||||
Response response = mapper.toResponse(ex);
|
||||
assertThat(response.getStatus()).isEqualTo(400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
package dev.lions.unionflow.server.filter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import dev.lions.unionflow.server.service.SystemLoggingService;
|
||||
import jakarta.ws.rs.container.ContainerRequestContext;
|
||||
import jakarta.ws.rs.container.ContainerResponseContext;
|
||||
import jakarta.ws.rs.core.SecurityContext;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.Principal;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour HttpLoggingFilter (sans @QuarkusTest pour contrôle total).
|
||||
* Couvre les méthodes privées et toutes les branches via réflexion + mocks Mockito.
|
||||
*/
|
||||
class HttpLoggingFilterTest {
|
||||
|
||||
HttpLoggingFilter filter;
|
||||
SystemLoggingService mockLoggingService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
filter = new HttpLoggingFilter();
|
||||
mockLoggingService = mock(SystemLoggingService.class);
|
||||
|
||||
Field field = HttpLoggingFilter.class.getDeclaredField("systemLoggingService");
|
||||
field.setAccessible(true);
|
||||
field.set(filter, mockLoggingService);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// filter(ContainerRequestContext) — request filter
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("filter request — enregistre startTime, method et path dans les propriétés")
|
||||
void requestFilter_setsProperties() throws Exception {
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
UriInfo uriInfo = mock(UriInfo.class);
|
||||
when(ctx.getUriInfo()).thenReturn(uriInfo);
|
||||
when(uriInfo.getPath()).thenReturn("api/test");
|
||||
when(ctx.getMethod()).thenReturn("GET");
|
||||
|
||||
assertThatCode(() -> filter.filter(ctx)).doesNotThrowAnyException();
|
||||
|
||||
verify(ctx).setProperty(eq("REQUEST_START_TIME"), anyLong());
|
||||
verify(ctx).setProperty("REQUEST_METHOD", "GET");
|
||||
verify(ctx).setProperty("REQUEST_PATH", "api/test");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// filter(request, response) — response filter avec path api/ → shouldLog=true
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("filter response — path api/ → shouldLog true → logRequest appelé")
|
||||
void responseFilter_apiPath_logsRequest() throws Exception {
|
||||
ContainerRequestContext reqCtx = mock(ContainerRequestContext.class);
|
||||
ContainerResponseContext resCtx = mock(ContainerResponseContext.class);
|
||||
|
||||
when(reqCtx.getProperty("REQUEST_START_TIME")).thenReturn(System.currentTimeMillis() - 100);
|
||||
when(reqCtx.getProperty("REQUEST_METHOD")).thenReturn("GET");
|
||||
when(reqCtx.getProperty("REQUEST_PATH")).thenReturn("api/membres");
|
||||
when(resCtx.getStatus()).thenReturn(200);
|
||||
when(reqCtx.getSecurityContext()).thenReturn(null);
|
||||
when(reqCtx.getHeaderString(anyString())).thenReturn(null);
|
||||
|
||||
filter.filter(reqCtx, resCtx);
|
||||
|
||||
verify(mockLoggingService).logRequest(anyString(), anyString(), anyInt(), anyString(), anyString(), any(), anyLong());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// filter(request, response) — startTime null → durationMs = 0
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("filter response — startTime null → durationMs = 0")
|
||||
void responseFilter_nullStartTime_durationIsZero() throws Exception {
|
||||
ContainerRequestContext reqCtx = mock(ContainerRequestContext.class);
|
||||
ContainerResponseContext resCtx = mock(ContainerResponseContext.class);
|
||||
|
||||
when(reqCtx.getProperty("REQUEST_START_TIME")).thenReturn(null);
|
||||
when(reqCtx.getProperty("REQUEST_METHOD")).thenReturn("POST");
|
||||
when(reqCtx.getProperty("REQUEST_PATH")).thenReturn("api/test");
|
||||
when(resCtx.getStatus()).thenReturn(201);
|
||||
when(reqCtx.getSecurityContext()).thenReturn(null);
|
||||
when(reqCtx.getHeaderString(anyString())).thenReturn(null);
|
||||
|
||||
filter.filter(reqCtx, resCtx);
|
||||
|
||||
verify(mockLoggingService).logRequest(eq("POST"), eq("/api/test"), eq(201), eq("anonymous"), eq("unknown"), isNull(), eq(0L));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// filter(request, response) — path q/ → shouldLog false → pas de log
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("filter response — path q/ → shouldLog false → logRequest non appelé")
|
||||
void responseFilter_qPath_doesNotLog() throws Exception {
|
||||
ContainerRequestContext reqCtx = mock(ContainerRequestContext.class);
|
||||
ContainerResponseContext resCtx = mock(ContainerResponseContext.class);
|
||||
|
||||
when(reqCtx.getProperty("REQUEST_START_TIME")).thenReturn(System.currentTimeMillis());
|
||||
when(reqCtx.getProperty("REQUEST_METHOD")).thenReturn("GET");
|
||||
when(reqCtx.getProperty("REQUEST_PATH")).thenReturn("q/health");
|
||||
when(resCtx.getStatus()).thenReturn(200);
|
||||
|
||||
filter.filter(reqCtx, resCtx);
|
||||
|
||||
verify(mockLoggingService, never()).logRequest(any(), any(), anyInt(), any(), any(), any(), anyLong());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// shouldLog — branches
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("shouldLog null → false")
|
||||
void shouldLog_null_returnsFalse() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("shouldLog", String.class);
|
||||
m.setAccessible(true);
|
||||
assertThat((Boolean) m.invoke(filter, (String) null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("shouldLog 'q/health' → false")
|
||||
void shouldLog_qPath_returnsFalse() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("shouldLog", String.class);
|
||||
m.setAccessible(true);
|
||||
assertThat((Boolean) m.invoke(filter, "q/health")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("shouldLog 'static/img.png' → false")
|
||||
void shouldLog_staticPath_returnsFalse() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("shouldLog", String.class);
|
||||
m.setAccessible(true);
|
||||
assertThat((Boolean) m.invoke(filter, "static/img.png")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("shouldLog 'webjars/jquery.js' → false")
|
||||
void shouldLog_webjarsPath_returnsFalse() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("shouldLog", String.class);
|
||||
m.setAccessible(true);
|
||||
assertThat((Boolean) m.invoke(filter, "webjars/jquery.js")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("shouldLog 'api/membres' → true")
|
||||
void shouldLog_apiPath_returnsTrue() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("shouldLog", String.class);
|
||||
m.setAccessible(true);
|
||||
assertThat((Boolean) m.invoke(filter, "api/membres")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("shouldLog 'other/path' → false (ne commence pas par api/)")
|
||||
void shouldLog_otherPath_returnsFalse() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("shouldLog", String.class);
|
||||
m.setAccessible(true);
|
||||
assertThat((Boolean) m.invoke(filter, "other/path")).isFalse();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// extractUserId — branches
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("extractUserId — securityContext null → 'anonymous'")
|
||||
void extractUserId_nullSecurityContext_returnsAnonymous() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractUserId", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
when(ctx.getSecurityContext()).thenReturn(null);
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isEqualTo("anonymous");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("extractUserId — securityContext présent mais principal null → 'anonymous'")
|
||||
void extractUserId_nullPrincipal_returnsAnonymous() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractUserId", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
SecurityContext sc = mock(SecurityContext.class);
|
||||
when(ctx.getSecurityContext()).thenReturn(sc);
|
||||
when(sc.getUserPrincipal()).thenReturn(null);
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isEqualTo("anonymous");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("extractUserId — principal non null → retourne le nom du principal")
|
||||
void extractUserId_withPrincipal_returnsPrincipalName() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractUserId", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
SecurityContext sc = mock(SecurityContext.class);
|
||||
Principal principal = mock(Principal.class);
|
||||
when(ctx.getSecurityContext()).thenReturn(sc);
|
||||
when(sc.getUserPrincipal()).thenReturn(principal);
|
||||
when(principal.getName()).thenReturn("alice@test.com");
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isEqualTo("alice@test.com");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// extractIpAddress — branches
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("extractIpAddress — X-Forwarded-For présent → première IP")
|
||||
void extractIpAddress_xForwardedFor_returnsFirstIp() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractIpAddress", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
when(ctx.getHeaderString("X-Forwarded-For")).thenReturn("10.0.0.1, 10.0.0.2");
|
||||
when(ctx.getHeaderString("X-Real-IP")).thenReturn(null);
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isEqualTo("10.0.0.1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("extractIpAddress — X-Real-IP présent → retourne X-Real-IP")
|
||||
void extractIpAddress_xRealIp_returnsRealIp() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractIpAddress", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
when(ctx.getHeaderString("X-Forwarded-For")).thenReturn(null);
|
||||
when(ctx.getHeaderString("X-Real-IP")).thenReturn("192.168.1.50");
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isEqualTo("192.168.1.50");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("extractIpAddress — aucun header → 'unknown'")
|
||||
void extractIpAddress_noHeaders_returnsUnknown() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractIpAddress", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
when(ctx.getHeaderString("X-Forwarded-For")).thenReturn(null);
|
||||
when(ctx.getHeaderString("X-Real-IP")).thenReturn(null);
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isEqualTo("unknown");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// extractSessionId — branches
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("extractSessionId — X-Session-ID présent → retourne la valeur")
|
||||
void extractSessionId_withHeader_returnsSessionId() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractSessionId", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
when(ctx.getHeaderString("X-Session-ID")).thenReturn("session-abc-123");
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isEqualTo("session-abc-123");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("extractSessionId — X-Session-ID absent → null")
|
||||
void extractSessionId_noHeader_returnsNull() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractSessionId", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
when(ctx.getHeaderString("X-Session-ID")).thenReturn(null);
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isNull();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// extractIpAddress — branches manquantes: header non-null mais vide
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("extractIpAddress — X-Forwarded-For vide (isEmpty) → tente X-Real-IP")
|
||||
void extractIpAddress_emptyXForwardedFor_fallsThrough() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractIpAddress", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
// X-Forwarded-For non-null mais vide → condition !xForwardedFor.isEmpty() = false → tente X-Real-IP
|
||||
when(ctx.getHeaderString("X-Forwarded-For")).thenReturn("");
|
||||
when(ctx.getHeaderString("X-Real-IP")).thenReturn("10.10.10.10");
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isEqualTo("10.10.10.10");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("extractIpAddress — X-Forwarded-For vide + X-Real-IP vide → 'unknown'")
|
||||
void extractIpAddress_emptyXForwardedFor_emptyXRealIp_returnsUnknown() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractIpAddress", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
// Les deux headers non-null mais vides → condition !isEmpty() = false pour les deux → "unknown"
|
||||
when(ctx.getHeaderString("X-Forwarded-For")).thenReturn("");
|
||||
when(ctx.getHeaderString("X-Real-IP")).thenReturn("");
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isEqualTo("unknown");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("extractSessionId — X-Session-ID vide (isEmpty) → null")
|
||||
void extractSessionId_emptyHeader_returnsNull() throws Exception {
|
||||
Method m = HttpLoggingFilter.class.getDeclaredMethod("extractSessionId", ContainerRequestContext.class);
|
||||
m.setAccessible(true);
|
||||
|
||||
ContainerRequestContext ctx = mock(ContainerRequestContext.class);
|
||||
// X-Session-ID non-null mais vide → retourne null
|
||||
when(ctx.getHeaderString("X-Session-ID")).thenReturn("");
|
||||
|
||||
assertThat((String) m.invoke(filter, ctx)).isNull();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// filter(request, response) — catch block quand logRequest lève une exception
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("filter response — exception dans logRequest est capturée (catch block)")
|
||||
void responseFilter_exceptionInLog_isCaught() throws Exception {
|
||||
ContainerRequestContext reqCtx = mock(ContainerRequestContext.class);
|
||||
ContainerResponseContext resCtx = mock(ContainerResponseContext.class);
|
||||
|
||||
when(reqCtx.getProperty("REQUEST_START_TIME")).thenReturn(System.currentTimeMillis());
|
||||
when(reqCtx.getProperty("REQUEST_METHOD")).thenReturn("GET");
|
||||
when(reqCtx.getProperty("REQUEST_PATH")).thenReturn("api/test");
|
||||
when(resCtx.getStatus()).thenReturn(200);
|
||||
when(reqCtx.getSecurityContext()).thenReturn(null);
|
||||
when(reqCtx.getHeaderString(anyString())).thenReturn(null);
|
||||
doThrow(new RuntimeException("log error")).when(mockLoggingService)
|
||||
.logRequest(any(), any(), anyInt(), any(), any(), any(), anyLong());
|
||||
|
||||
// Ne doit pas propager l'exception (catch block)
|
||||
assertThatCode(() -> filter.filter(reqCtx, resCtx)).doesNotThrowAnyException();
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,35 @@
|
||||
package dev.lions.unionflow.server.mapper;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest;
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.request.UpdateDemandeAideRequest;
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
|
||||
import dev.lions.unionflow.server.entity.DemandeAide;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@QuarkusTest
|
||||
class DemandeAideMapperTest {
|
||||
|
||||
@Inject
|
||||
DemandeAideMapper mapper;
|
||||
|
||||
// ================================================================
|
||||
// toDTO
|
||||
// ================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("toDTO avec null retourne null")
|
||||
void toDTO_null_returnsNull() {
|
||||
@@ -44,4 +56,221 @@ class DemandeAideMapperTest {
|
||||
assertThat(dto.getDescription()).isEqualTo("Description");
|
||||
assertThat(dto.getStatut()).isEqualTo(StatutAide.EN_ATTENTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDTO avec urgence null mappe PrioriteAide.NORMALE (branche null L50)")
|
||||
void toDTO_urgenceNull_mappesNormale() {
|
||||
DemandeAide entity = new DemandeAide();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setTitre("Test urgence null");
|
||||
entity.setDescription("Desc");
|
||||
entity.setStatut(StatutAide.EN_ATTENTE);
|
||||
entity.setUrgence(null); // urgence == null → PrioriteAide.NORMALE
|
||||
|
||||
DemandeAideResponse dto = mapper.toDTO(entity);
|
||||
|
||||
assertThat(dto.getPriorite()).isEqualTo(PrioriteAide.NORMALE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDTO avec urgence=true mappe PrioriteAide.URGENTE")
|
||||
void toDTO_urgenceTrue_mappesUrgente() {
|
||||
DemandeAide entity = new DemandeAide();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setTitre("Test urgence true");
|
||||
entity.setDescription("Desc");
|
||||
entity.setStatut(StatutAide.EN_ATTENTE);
|
||||
entity.setUrgence(true);
|
||||
|
||||
DemandeAideResponse dto = mapper.toDTO(entity);
|
||||
|
||||
assertThat(dto.getPriorite()).isEqualTo(PrioriteAide.URGENTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDTO avec évaluateur non null mappe les champs évaluateur (L59-61)")
|
||||
void toDTO_evaluateurNonNull_mappeEvaluateur() {
|
||||
Membre evaluateur = new Membre();
|
||||
evaluateur.setId(UUID.randomUUID());
|
||||
evaluateur.setPrenom("Marie");
|
||||
evaluateur.setNom("Curie");
|
||||
|
||||
DemandeAide entity = new DemandeAide();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setTitre("Test évaluateur");
|
||||
entity.setDescription("Desc");
|
||||
entity.setStatut(StatutAide.EN_COURS_EVALUATION);
|
||||
entity.setEvaluateur(evaluateur);
|
||||
|
||||
DemandeAideResponse dto = mapper.toDTO(entity);
|
||||
|
||||
assertThat(dto.getEvaluateurId()).isEqualTo(evaluateur.getId().toString());
|
||||
assertThat(dto.getEvaluateurNom()).isEqualTo("Marie Curie");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDTO avec demandeur et organisation non null mappe tous les champs")
|
||||
void toDTO_demandeurEtOrganisationNonNull_mappeTousLesChamps() {
|
||||
Membre demandeur = new Membre();
|
||||
demandeur.setId(UUID.randomUUID());
|
||||
demandeur.setPrenom("Jean");
|
||||
demandeur.setNom("Dupont");
|
||||
demandeur.setNumeroMembre("M-001");
|
||||
|
||||
Organisation organisation = new Organisation();
|
||||
organisation.setId(UUID.randomUUID());
|
||||
organisation.setNom("Asso Solidaire");
|
||||
|
||||
DemandeAide entity = new DemandeAide();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setTitre("Test complet");
|
||||
entity.setDescription("Desc");
|
||||
entity.setStatut(StatutAide.EN_ATTENTE);
|
||||
entity.setDemandeur(demandeur);
|
||||
entity.setOrganisation(organisation);
|
||||
|
||||
DemandeAideResponse dto = mapper.toDTO(entity);
|
||||
|
||||
assertThat(dto.getMembreDemandeurId()).isEqualTo(demandeur.getId());
|
||||
assertThat(dto.getNomDemandeur()).isEqualTo("Jean Dupont");
|
||||
assertThat(dto.getNumeroMembreDemandeur()).isEqualTo("M-001");
|
||||
assertThat(dto.getAssociationId()).isEqualTo(organisation.getId());
|
||||
assertThat(dto.getNomAssociation()).isEqualTo("Asso Solidaire");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// updateEntityFromDTO
|
||||
// ================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDTO avec entity null retourne sans exception (L75)")
|
||||
void updateEntityFromDTO_entityNull_returnsWithoutException() {
|
||||
UpdateDemandeAideRequest request = UpdateDemandeAideRequest.builder()
|
||||
.titre("Titre")
|
||||
.build();
|
||||
// No exception should be thrown
|
||||
mapper.updateEntityFromDTO(null, request);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDTO avec request null retourne sans exception (L75)")
|
||||
void updateEntityFromDTO_requestNull_returnsWithoutException() {
|
||||
DemandeAide entity = new DemandeAide();
|
||||
entity.setTitre("Original");
|
||||
// No exception should be thrown
|
||||
mapper.updateEntityFromDTO(entity, null);
|
||||
assertThat(entity.getTitre()).isEqualTo("Original"); // unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDTO avec description, typeAide, statut, dateSoumission non null met à jour (L81,L84,L87,L96)")
|
||||
void updateEntityFromDTO_allNonNullFields_updatesAll() {
|
||||
DemandeAide entity = new DemandeAide();
|
||||
entity.setTitre("Titre original");
|
||||
entity.setDescription("Description originale");
|
||||
entity.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
|
||||
entity.setStatut(StatutAide.EN_ATTENTE);
|
||||
|
||||
LocalDateTime dateSoumission = LocalDateTime.now().minusDays(1);
|
||||
UpdateDemandeAideRequest request = UpdateDemandeAideRequest.builder()
|
||||
.titre("Nouveau titre")
|
||||
.description("Nouvelle description")
|
||||
.typeAide(TypeAide.TRANSPORT)
|
||||
.statut(StatutAide.EN_COURS_EVALUATION)
|
||||
.priorite(PrioriteAide.URGENTE)
|
||||
.dateSoumission(dateSoumission)
|
||||
.montantDemande(new BigDecimal("5000"))
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDTO(entity, request);
|
||||
|
||||
assertThat(entity.getTitre()).isEqualTo("Nouveau titre");
|
||||
assertThat(entity.getDescription()).isEqualTo("Nouvelle description");
|
||||
assertThat(entity.getTypeAide()).isEqualTo(TypeAide.TRANSPORT);
|
||||
assertThat(entity.getStatut()).isEqualTo(StatutAide.EN_COURS_EVALUATION);
|
||||
assertThat(entity.getUrgence()).isTrue(); // URGENTE.isUrgente() = true
|
||||
assertThat(entity.getDateDemande()).isEqualTo(dateSoumission);
|
||||
assertThat(entity.getMontantDemande()).isEqualByComparingTo(new BigDecimal("5000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDTO avec priorite null → urgence = false (L95 false branch)")
|
||||
void updateEntityFromDTO_prioriteNull_urgenceFalse() {
|
||||
DemandeAide entity = new DemandeAide();
|
||||
entity.setUrgence(true); // Start as urgent
|
||||
|
||||
UpdateDemandeAideRequest request = UpdateDemandeAideRequest.builder()
|
||||
.priorite(null) // null → urgence = false (null && ... = false)
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDTO(entity, request);
|
||||
|
||||
assertThat(entity.getUrgence()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDTO avec priorite=NORMALE → urgence = false (L95 isUrgente()=false branch)")
|
||||
void updateEntityFromDTO_prioriteNormale_urgenceFalse() {
|
||||
DemandeAide entity = new DemandeAide();
|
||||
entity.setUrgence(true); // Start as urgent
|
||||
|
||||
UpdateDemandeAideRequest request = UpdateDemandeAideRequest.builder()
|
||||
.priorite(PrioriteAide.NORMALE) // non-null but isUrgente() = false → urgence = false
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDTO(entity, request);
|
||||
|
||||
assertThat(entity.getUrgence()).isFalse();
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// toEntity
|
||||
// ================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("toEntity avec request null retourne null (L111)")
|
||||
void toEntity_requestNull_returnsNull() {
|
||||
DemandeAide entity = mapper.toEntity(null, null, null, null);
|
||||
assertThat(entity).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toEntity avec request valide crée une entité correcte")
|
||||
void toEntity_requestValide_creeEntite() {
|
||||
CreateDemandeAideRequest request = CreateDemandeAideRequest.builder()
|
||||
.titre("Aide test")
|
||||
.description("Description")
|
||||
.typeAide(TypeAide.AIDE_ALIMENTAIRE)
|
||||
.priorite(PrioriteAide.URGENTE)
|
||||
.montantDemande(new BigDecimal("1000"))
|
||||
.membreDemandeurId(UUID.randomUUID())
|
||||
.associationId(UUID.randomUUID())
|
||||
.build();
|
||||
|
||||
DemandeAide entity = mapper.toEntity(request, null, null, null);
|
||||
|
||||
assertThat(entity).isNotNull();
|
||||
assertThat(entity.getTitre()).isEqualTo("Aide test");
|
||||
assertThat(entity.getTypeAide()).isEqualTo(TypeAide.AIDE_ALIMENTAIRE);
|
||||
assertThat(entity.getUrgence()).isTrue(); // URGENTE.isUrgente() = true
|
||||
assertThat(entity.getStatut()).isEqualTo(StatutAide.EN_ATTENTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toEntity avec priorite null → urgence = false (L121 false branch)")
|
||||
void toEntity_prioriteNull_urgenceFalse() {
|
||||
CreateDemandeAideRequest request = CreateDemandeAideRequest.builder()
|
||||
.titre("Aide priorite null")
|
||||
.description("Desc")
|
||||
.typeAide(TypeAide.TRANSPORT)
|
||||
.priorite(null) // null → urgence = false
|
||||
.membreDemandeurId(UUID.randomUUID())
|
||||
.associationId(UUID.randomUUID())
|
||||
.build();
|
||||
|
||||
DemandeAide entity = mapper.toEntity(request, null, null, null);
|
||||
|
||||
assertThat(entity).isNotNull();
|
||||
assertThat(entity.getUrgence()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,4 +92,35 @@ class CampagneAgricoleMapperTest {
|
||||
assertThat(entity.getDesignation()).isEqualTo("Nouvelle désignation");
|
||||
assertThat(entity.getStatut()).isEqualTo(StatutCampagneAgricole.CLOTUREE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité")
|
||||
void updateEntityFromDto_nullDto_noOp() {
|
||||
CampagneAgricole entity = CampagneAgricole.builder()
|
||||
.designation("Inchangée")
|
||||
.statut(StatutCampagneAgricole.PREPARATION)
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getDesignation()).isEqualTo("Inchangée");
|
||||
assertThat(entity.getStatut()).isEqualTo(StatutCampagneAgricole.PREPARATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec entity sans organisation met organisationCoopId à null")
|
||||
void toDto_entityWithNullOrganisation_nullOrganisationId() {
|
||||
CampagneAgricole entity = CampagneAgricole.builder()
|
||||
.designation("Sans org")
|
||||
.statut(StatutCampagneAgricole.PREPARATION)
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
// organisation est null → entityOrganisationId retourne null → organisationCoopId non setté
|
||||
|
||||
CampagneAgricoleDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getOrganisationCoopId()).isNull();
|
||||
assertThat(dto.getDesignation()).isEqualTo("Sans org");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +72,35 @@ class CampagneCollecteMapperTest {
|
||||
assertThat(entity.getTitre()).isEqualTo("Nouveau titre");
|
||||
assertThat(entity.getObjectifFinancier()).isEqualByComparingTo("500000");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité")
|
||||
void updateEntityFromDto_nullDto_noOp() {
|
||||
CampagneCollecte entity = CampagneCollecte.builder()
|
||||
.titre("Inchangé")
|
||||
.objectifFinancier(new BigDecimal("200000"))
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getTitre()).isEqualTo("Inchangé");
|
||||
assertThat(entity.getObjectifFinancier()).isEqualByComparingTo("200000");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec entity sans organisation met organisationId à null")
|
||||
void toDto_entityWithNullOrganisation_nullOrganisationId() {
|
||||
CampagneCollecte entity = CampagneCollecte.builder()
|
||||
.titre("Sans org")
|
||||
.statut(StatutCampagneCollecte.EN_COURS)
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
// organisation est null → entityOrganisationId retourne null → organisationId non setté
|
||||
|
||||
CampagneCollecteResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getOrganisationId()).isNull();
|
||||
assertThat(dto.getTitre()).isEqualTo("Sans org");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,29 @@ class ContributionCollecteMapperTest {
|
||||
assertThat(entity.getMembreDonateur()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec campagne null produit campagneId null (branche entityCampagneId — objet null)")
|
||||
void toDto_campagneNull_campagneIdIsNull() {
|
||||
UUID membreId = UUID.randomUUID();
|
||||
Membre membre = new Membre();
|
||||
membre.setId(membreId);
|
||||
ContributionCollecte entity = ContributionCollecte.builder()
|
||||
.campagne(null)
|
||||
.membreDonateur(membre)
|
||||
.aliasDonateur("Sans campagne")
|
||||
.estAnonyme(false)
|
||||
.montantSoutien(new BigDecimal("5000"))
|
||||
.dateContribution(LocalDateTime.now())
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
ContributionCollecteDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getCampagneId()).isNull();
|
||||
assertThat(dto.getMembreDonateurId()).isEqualTo(membreId.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto met à jour l'entité cible")
|
||||
void updateEntityFromDto_updatesTarget() {
|
||||
@@ -99,4 +122,18 @@ class ContributionCollecteMapperTest {
|
||||
assertThat(entity.getAliasDonateur()).isEqualTo("Nouveau");
|
||||
assertThat(entity.getMontantSoutien()).isEqualByComparingTo("25000");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité (branche updateEntityFromDto — dto null)")
|
||||
void updateEntityFromDto_dtoNull_entityUnchanged() {
|
||||
ContributionCollecte entity = ContributionCollecte.builder()
|
||||
.aliasDonateur("Alias stable")
|
||||
.montantSoutien(new BigDecimal("12000"))
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getAliasDonateur()).isEqualTo("Alias stable");
|
||||
assertThat(entity.getMontantSoutien()).isEqualByComparingTo("12000");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package dev.lions.unionflow.server.mapper.culte;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.culte.DonReligieuxDTO;
|
||||
import dev.lions.unionflow.server.api.enums.culte.TypeDonReligieux;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.entity.culte.DonReligieux;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
@@ -68,4 +69,91 @@ class DonReligieuxMapperTest {
|
||||
assertThat(entity.getTypeDon()).isEqualTo(TypeDonReligieux.DIME);
|
||||
assertThat(entity.getMontant()).isEqualByComparingTo(BigDecimal.valueOf(100));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto met à jour les champs de l'entité")
|
||||
void updateEntityFromDto_updatesEntity() {
|
||||
DonReligieux entity = DonReligieux.builder()
|
||||
.typeDon(TypeDonReligieux.QUETE_ORDINAIRE)
|
||||
.montant(BigDecimal.ONE)
|
||||
.build();
|
||||
|
||||
DonReligieuxDTO dto = new DonReligieuxDTO();
|
||||
dto.setTypeDon(TypeDonReligieux.DIME);
|
||||
dto.setMontant(BigDecimal.valueOf(500));
|
||||
|
||||
mapper.updateEntityFromDto(dto, entity);
|
||||
|
||||
assertThat(entity.getTypeDon()).isEqualTo(TypeDonReligieux.DIME);
|
||||
assertThat(entity.getMontant()).isEqualByComparingTo(BigDecimal.valueOf(500));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Branche manquante : updateEntityFromDto — dto null → early return (ligne 68)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité (branche null-guard)")
|
||||
void updateEntityFromDto_dtoNull_entityInchangee() {
|
||||
DonReligieux entity = DonReligieux.builder()
|
||||
.typeDon(TypeDonReligieux.QUETE_ORDINAIRE)
|
||||
.montant(BigDecimal.TEN)
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
// L'entité ne doit pas avoir été modifiée
|
||||
assertThat(entity.getTypeDon()).isEqualTo(TypeDonReligieux.QUETE_ORDINAIRE);
|
||||
assertThat(entity.getMontant()).isEqualByComparingTo(BigDecimal.TEN);
|
||||
}
|
||||
|
||||
// ==================== Tests: toDto — branches manquantes (institution null, fidele non-null) ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec institution null ne renseigne pas institutionId dans le DTO")
|
||||
void toDto_institutionNull_institutionIdAbsentDuDto() {
|
||||
DonReligieux entity = DonReligieux.builder()
|
||||
.institution(null)
|
||||
.fidele(null)
|
||||
.typeDon(TypeDonReligieux.OFFRANDE_SPECIALE)
|
||||
.montant(BigDecimal.valueOf(50))
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
DonReligieuxDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getInstitutionId()).isNull();
|
||||
assertThat(dto.getFideleId()).isNull();
|
||||
assertThat(dto.getTypeDon()).isEqualTo(TypeDonReligieux.OFFRANDE_SPECIALE);
|
||||
assertThat(dto.getMontant()).isEqualByComparingTo(BigDecimal.valueOf(50));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec fidele non-null renseigne fideleId dans le DTO")
|
||||
void toDto_fideleNonNull_fideleIdRenseigneDansDto() {
|
||||
UUID instId = UUID.randomUUID();
|
||||
Organisation inst = new Organisation();
|
||||
inst.setId(instId);
|
||||
|
||||
UUID fideleId = UUID.randomUUID();
|
||||
Membre fidele = new Membre();
|
||||
fidele.setId(fideleId);
|
||||
|
||||
DonReligieux entity = DonReligieux.builder()
|
||||
.institution(inst)
|
||||
.fidele(fidele)
|
||||
.typeDon(TypeDonReligieux.DIME)
|
||||
.montant(BigDecimal.valueOf(1000))
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
DonReligieuxDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getInstitutionId()).isEqualTo(instId.toString());
|
||||
assertThat(dto.getFideleId()).isEqualTo(fideleId.toString());
|
||||
assertThat(dto.getTypeDon()).isEqualTo(TypeDonReligieux.DIME);
|
||||
assertThat(dto.getMontant()).isEqualByComparingTo(BigDecimal.valueOf(1000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +90,74 @@ class EchelonOrganigrammeMapperTest {
|
||||
assertThat(entity.getDesignation()).isEqualTo("Nouvelle désignation");
|
||||
assertThat(entity.getNiveau()).isEqualTo(NiveauEchelon.SIEGE_MONDIAL);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Branche manquante : updateEntityFromDto — dto null → early return (ligne 65)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité (branche null-guard)")
|
||||
void updateEntityFromDto_dtoNull_entityInchangee() {
|
||||
EchelonOrganigramme entity = EchelonOrganigramme.builder()
|
||||
.designation("Invariante")
|
||||
.niveau(NiveauEchelon.NATIONAL)
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getDesignation()).isEqualTo("Invariante");
|
||||
assertThat(entity.getNiveau()).isEqualTo(NiveauEchelon.NATIONAL);
|
||||
}
|
||||
|
||||
// ==================== Tests: toDto — branches manquantes (organisation null, echelonParent non-null) ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec organisation null ne renseigne pas organisationId dans le DTO")
|
||||
void toDto_organisationNull_organisationIdAbsentDuDto() {
|
||||
EchelonOrganigramme entity = EchelonOrganigramme.builder()
|
||||
.organisation(null)
|
||||
.echelonParent(null)
|
||||
.niveau(NiveauEchelon.NATIONAL)
|
||||
.designation("Échelon sans organisation")
|
||||
.zoneGeographiqueOuDelegation("Zone B")
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
EchelonOrganigrammeDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getOrganisationId()).isNull();
|
||||
assertThat(dto.getEchelonParentId()).isNull();
|
||||
assertThat(dto.getDesignation()).isEqualTo("Échelon sans organisation");
|
||||
assertThat(dto.getNiveau()).isEqualTo(NiveauEchelon.NATIONAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec echelonParent non-null renseigne echelonParentId dans le DTO")
|
||||
void toDto_echelonParentNonNull_echelonParentIdRenseigneDansDto() {
|
||||
UUID parentId = UUID.randomUUID();
|
||||
Organisation parent = new Organisation();
|
||||
parent.setId(parentId);
|
||||
|
||||
UUID orgId = UUID.randomUUID();
|
||||
Organisation org = new Organisation();
|
||||
org.setId(orgId);
|
||||
|
||||
EchelonOrganigramme entity = EchelonOrganigramme.builder()
|
||||
.organisation(org)
|
||||
.echelonParent(parent)
|
||||
.niveau(NiveauEchelon.REGIONAL)
|
||||
.designation("Échelon régional")
|
||||
.zoneGeographiqueOuDelegation("Région Nord")
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
EchelonOrganigrammeDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getOrganisationId()).isEqualTo(orgId.toString());
|
||||
assertThat(dto.getEchelonParentId()).isEqualTo(parentId.toString());
|
||||
assertThat(dto.getDesignation()).isEqualTo("Échelon régional");
|
||||
assertThat(dto.getNiveau()).isEqualTo(NiveauEchelon.REGIONAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,21 @@ package dev.lions.unionflow.server.mapper.mutuelle.credit;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.mutuelle.credit.DemandeCreditRequest;
|
||||
import dev.lions.unionflow.server.api.dto.mutuelle.credit.DemandeCreditResponse;
|
||||
import dev.lions.unionflow.server.api.enums.mutuelle.credit.StatutEcheanceCredit;
|
||||
import dev.lions.unionflow.server.api.enums.mutuelle.credit.TypeCredit;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
|
||||
import dev.lions.unionflow.server.entity.mutuelle.credit.DemandeCredit;
|
||||
import dev.lions.unionflow.server.entity.mutuelle.credit.EcheanceCredit;
|
||||
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -111,4 +115,97 @@ class DemandeCreditMapperTest {
|
||||
assertThat(entity.getMontantDemande()).isEqualByComparingTo("200000");
|
||||
assertThat(entity.getJustificationDetaillee()).isEqualTo("Nouvelle justification");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Branche manquante : updateEntityFromDto — request null → early return (ligne 87)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec request null ne modifie pas l'entité (branche null-guard)")
|
||||
void updateEntityFromDto_requestNull_entityInchangee() {
|
||||
DemandeCredit entity = DemandeCredit.builder()
|
||||
.typeCredit(TypeCredit.CONSOMMATION)
|
||||
.montantDemande(new BigDecimal("50000"))
|
||||
.dureeMoisDemande(12)
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getTypeCredit()).isEqualTo(TypeCredit.CONSOMMATION);
|
||||
assertThat(entity.getMontantDemande()).isEqualByComparingTo("50000");
|
||||
assertThat(entity.getDureeMoisDemande()).isEqualTo(12);
|
||||
}
|
||||
|
||||
// ==================== Tests: echeanceCreditListToEcheanceCreditDTOList (branches manquantes) ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec echeancier null retourne echeancier null dans le DTO")
|
||||
void toDto_echeancierNull_returnsDtoWithNullEcheancier() {
|
||||
DemandeCredit entity = new DemandeCredit();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setTypeCredit(TypeCredit.CONSOMMATION);
|
||||
entity.setMontantDemande(new BigDecimal("100000"));
|
||||
entity.setEcheancier(null);
|
||||
|
||||
DemandeCreditResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getEcheancier()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec echeancier vide retourne liste vide dans le DTO")
|
||||
void toDto_echeancierVide_returnsDtoWithEmptyEcheancier() {
|
||||
DemandeCredit entity = DemandeCredit.builder()
|
||||
.typeCredit(TypeCredit.CONSOMMATION)
|
||||
.montantDemande(new BigDecimal("200000"))
|
||||
.dureeMoisDemande(12)
|
||||
.echeancier(Collections.emptyList())
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
DemandeCreditResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getEcheancier()).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec echeancier contenant des échéances mappe chaque élément")
|
||||
void toDto_echeancierAvecElements_mappeChaquEcheance() {
|
||||
EcheanceCredit echeance1 = EcheanceCredit.builder()
|
||||
.ordre(1)
|
||||
.dateEcheancePrevue(LocalDate.now().plusMonths(1))
|
||||
.capitalAmorti(new BigDecimal("10000"))
|
||||
.interetsDeLaPeriode(new BigDecimal("500"))
|
||||
.montantTotalExigible(new BigDecimal("10500"))
|
||||
.capitalRestantDu(new BigDecimal("90000"))
|
||||
.statut(StatutEcheanceCredit.A_VENIR)
|
||||
.build();
|
||||
echeance1.setId(UUID.randomUUID());
|
||||
|
||||
EcheanceCredit echeance2 = EcheanceCredit.builder()
|
||||
.ordre(2)
|
||||
.dateEcheancePrevue(LocalDate.now().plusMonths(2))
|
||||
.capitalAmorti(new BigDecimal("10000"))
|
||||
.interetsDeLaPeriode(new BigDecimal("450"))
|
||||
.montantTotalExigible(new BigDecimal("10450"))
|
||||
.capitalRestantDu(new BigDecimal("80000"))
|
||||
.statut(StatutEcheanceCredit.A_VENIR)
|
||||
.build();
|
||||
echeance2.setId(UUID.randomUUID());
|
||||
|
||||
DemandeCredit entity = new DemandeCredit();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setTypeCredit(TypeCredit.CONSOMMATION);
|
||||
entity.setMontantDemande(new BigDecimal("100000"));
|
||||
entity.setEcheancier(List.of(echeance1, echeance2));
|
||||
|
||||
DemandeCreditResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getEcheancier()).isNotNull().hasSize(2);
|
||||
assertThat(dto.getEcheancier().get(0).getOrdre()).isEqualTo(1);
|
||||
assertThat(dto.getEcheancier().get(1).getOrdre()).isEqualTo(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,4 +95,37 @@ class EcheanceCreditMapperTest {
|
||||
assertThat(entity.getOrdre()).isEqualTo(2);
|
||||
assertThat(entity.getStatut()).isEqualTo(StatutEcheanceCredit.PAYEE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité")
|
||||
void updateEntityFromDto_nullDto_noOp() {
|
||||
EcheanceCredit entity = EcheanceCredit.builder()
|
||||
.ordre(1)
|
||||
.statut(StatutEcheanceCredit.IMPAYEE)
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getOrdre()).isEqualTo(1);
|
||||
assertThat(entity.getStatut()).isEqualTo(StatutEcheanceCredit.IMPAYEE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec entity sans demandeCredit met demandeCreditId à null")
|
||||
void toDto_entityWithNullDemandeCredit_nullDemandeCreditId() {
|
||||
EcheanceCredit entity = EcheanceCredit.builder()
|
||||
.ordre(3)
|
||||
.capitalAmorti(new BigDecimal("5000"))
|
||||
.statut(StatutEcheanceCredit.PAYEE)
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
// demandeCredit est null → entityDemandeCreditId retourne null → demandeCreditId non setté
|
||||
|
||||
EcheanceCreditDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getDemandeCreditId()).isNull();
|
||||
assertThat(dto.getOrdre()).isEqualTo(3);
|
||||
assertThat(dto.getCapitalAmorti()).isEqualByComparingTo("5000");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,4 +85,18 @@ class GarantieDemandeMapperTest {
|
||||
assertThat(entity.getTypeGarantie()).isEqualTo(TypeGarantie.EPARGNE_BLOQUEE);
|
||||
assertThat(entity.getReferenceOuDescription()).isEqualTo("Nouvelle référence");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité")
|
||||
void updateEntityFromDto_nullDto_noOp() {
|
||||
GarantieDemande entity = GarantieDemande.builder()
|
||||
.typeGarantie(TypeGarantie.CAUTION_SOLIDAIRE)
|
||||
.referenceOuDescription("Inchangée")
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getTypeGarantie()).isEqualTo(TypeGarantie.CAUTION_SOLIDAIRE);
|
||||
assertThat(entity.getReferenceOuDescription()).isEqualTo("Inchangée");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,54 @@ class CompteEpargneMapperTest {
|
||||
assertThat(entity.getOrganisation()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec membre null produit membreId null (branche entityMembreId — objet null)")
|
||||
void toDto_membreNull_membreIdIsNull() {
|
||||
UUID orgId = UUID.randomUUID();
|
||||
Organisation org = new Organisation();
|
||||
org.setId(orgId);
|
||||
CompteEpargne entity = CompteEpargne.builder()
|
||||
.membre(null)
|
||||
.organisation(org)
|
||||
.numeroCompte("MEC-00999")
|
||||
.typeCompte(TypeCompteEpargne.EPARGNE_LIBRE)
|
||||
.soldeActuel(BigDecimal.ZERO)
|
||||
.soldeBloque(BigDecimal.ZERO)
|
||||
.statut(StatutCompteEpargne.ACTIF)
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
CompteEpargneResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getMembreId()).isNull();
|
||||
assertThat(dto.getOrganisationId()).isEqualTo(orgId.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec organisation null produit organisationId null (branche entityOrganisationId — objet null)")
|
||||
void toDto_organisationNull_organisationIdIsNull() {
|
||||
UUID membreId = UUID.randomUUID();
|
||||
Membre membre = new Membre();
|
||||
membre.setId(membreId);
|
||||
CompteEpargne entity = CompteEpargne.builder()
|
||||
.membre(membre)
|
||||
.organisation(null)
|
||||
.numeroCompte("MEC-00888")
|
||||
.typeCompte(TypeCompteEpargne.EPARGNE_BLOQUEE)
|
||||
.soldeActuel(new BigDecimal("20000"))
|
||||
.soldeBloque(BigDecimal.ZERO)
|
||||
.statut(StatutCompteEpargne.ACTIF)
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
CompteEpargneResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getMembreId()).isEqualTo(membreId.toString());
|
||||
assertThat(dto.getOrganisationId()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto met à jour l'entité cible")
|
||||
void updateEntityFromDto_updatesTarget() {
|
||||
@@ -106,4 +154,18 @@ class CompteEpargneMapperTest {
|
||||
assertThat(entity.getTypeCompte()).isEqualTo(TypeCompteEpargne.EPARGNE_BLOQUEE);
|
||||
assertThat(entity.getDescription()).isEqualTo("Nouvelles notes");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec request null ne modifie pas l'entité (branche updateEntityFromDto — dto null)")
|
||||
void updateEntityFromDto_requestNull_entityUnchanged() {
|
||||
CompteEpargne entity = CompteEpargne.builder()
|
||||
.typeCompte(TypeCompteEpargne.EPARGNE_LIBRE)
|
||||
.description("Description stable")
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getTypeCompte()).isEqualTo(TypeCompteEpargne.EPARGNE_LIBRE);
|
||||
assertThat(entity.getDescription()).isEqualTo("Description stable");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,4 +102,84 @@ class TransactionEpargneMapperTest {
|
||||
assertThat(entity.getMontant()).isEqualByComparingTo("75000");
|
||||
assertThat(entity.getMotif()).isEqualTo("Virement");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec request null ne modifie pas l'entité")
|
||||
void updateEntityFromDto_nullRequest_noOp() {
|
||||
TransactionEpargne entity = TransactionEpargne.builder()
|
||||
.type(TypeTransactionEpargne.DEPOT)
|
||||
.montant(new BigDecimal("10000"))
|
||||
.motif("Original")
|
||||
.build();
|
||||
|
||||
// doit retourner immédiatement sans modifier entity
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getType()).isEqualTo(TypeTransactionEpargne.DEPOT);
|
||||
assertThat(entity.getMontant()).isEqualByComparingTo("10000");
|
||||
assertThat(entity.getMotif()).isEqualTo("Original");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec entity sans compte (compte null) ne lève pas d'exception")
|
||||
void toDto_entityWithNullCompte_setsNullCompteId() {
|
||||
TransactionEpargne entity = TransactionEpargne.builder()
|
||||
.type(TypeTransactionEpargne.DEPOT)
|
||||
.montant(new BigDecimal("5000"))
|
||||
.motif("Sans compte")
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
// compte est null → entityCompteId retourne null → compteId non setté
|
||||
|
||||
TransactionEpargneResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getCompteId()).isNull();
|
||||
assertThat(dto.getMontant()).isEqualByComparingTo("5000");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec pieceJustificativeId non null produit pieceJustificativeId en String (branche expression Java — pieceJustificativeId non null)")
|
||||
void toDto_pieceJustificativeIdNonNull_convertsToString() {
|
||||
UUID compteId = UUID.randomUUID();
|
||||
CompteEpargne compte = new CompteEpargne();
|
||||
compte.setId(compteId);
|
||||
UUID pieceId = UUID.randomUUID();
|
||||
TransactionEpargne entity = TransactionEpargne.builder()
|
||||
.compte(compte)
|
||||
.type(TypeTransactionEpargne.DEPOT)
|
||||
.montant(new BigDecimal("100000"))
|
||||
.motif("Avec pièce justificative")
|
||||
.pieceJustificativeId(pieceId)
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
TransactionEpargneResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getPieceJustificativeId()).isEqualTo(pieceId.toString());
|
||||
assertThat(dto.getCompteId()).isEqualTo(compteId.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec pieceJustificativeId null produit pieceJustificativeId null (branche expression Java — pieceJustificativeId null)")
|
||||
void toDto_pieceJustificativeIdNull_setsNull() {
|
||||
UUID compteId = UUID.randomUUID();
|
||||
CompteEpargne compte = new CompteEpargne();
|
||||
compte.setId(compteId);
|
||||
TransactionEpargne entity = TransactionEpargne.builder()
|
||||
.compte(compte)
|
||||
.type(TypeTransactionEpargne.RETRAIT)
|
||||
.montant(new BigDecimal("30000"))
|
||||
.motif("Sans pièce")
|
||||
.pieceJustificativeId(null)
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
TransactionEpargneResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getPieceJustificativeId()).isNull();
|
||||
assertThat(dto.getMontant()).isEqualByComparingTo("30000");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,4 +67,55 @@ class ProjetOngMapperTest {
|
||||
assertThat(entity.getDescriptionMandat()).isEqualTo("Description");
|
||||
assertThat(entity.getZoneGeographiqueIntervention()).isEqualTo("Afrique");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec organisation null produit organisationId null (branche entityOrganisationId — objet null)")
|
||||
void toDto_organisationNull_organisationIdIsNull() {
|
||||
ProjetOng entity = ProjetOng.builder()
|
||||
.organisation(null)
|
||||
.nomProjet("Projet sans org")
|
||||
.statut(StatutProjetOng.EN_ETUDE)
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
ProjetOngDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getOrganisationId()).isNull();
|
||||
assertThat(dto.getNomProjet()).isEqualTo("Projet sans org");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto met à jour les champs de l'entité")
|
||||
void updateEntityFromDto_updatesEntity() {
|
||||
ProjetOng entity = ProjetOng.builder()
|
||||
.nomProjet("Ancien nom")
|
||||
.descriptionMandat("Ancienne description")
|
||||
.build();
|
||||
|
||||
ProjetOngDTO dto = new ProjetOngDTO();
|
||||
dto.setNomProjet("Nouveau nom");
|
||||
dto.setDescriptionMandat("Nouvelle description");
|
||||
dto.setZoneGeographiqueIntervention("Amérique");
|
||||
|
||||
mapper.updateEntityFromDto(dto, entity);
|
||||
|
||||
assertThat(entity.getNomProjet()).isEqualTo("Nouveau nom");
|
||||
assertThat(entity.getDescriptionMandat()).isEqualTo("Nouvelle description");
|
||||
assertThat(entity.getZoneGeographiqueIntervention()).isEqualTo("Amérique");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité (branche updateEntityFromDto — dto null)")
|
||||
void updateEntityFromDto_dtoNull_entityUnchanged() {
|
||||
ProjetOng entity = ProjetOng.builder()
|
||||
.nomProjet("Nom inchangé")
|
||||
.descriptionMandat("Description initiale")
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getNomProjet()).isEqualTo("Nom inchangé");
|
||||
assertThat(entity.getDescriptionMandat()).isEqualTo("Description initiale");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,4 +71,78 @@ class AgrementProfessionnelMapperTest {
|
||||
assertThat(entity.getStatut()).isEqualTo(StatutAgrement.PROVISOIRE);
|
||||
assertThat(entity.getSecteurOuOrdre()).isEqualTo("Juridique");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec membre null produit membreId null (branche entityMembreId — objet null)")
|
||||
void toDto_membreNull_membreIdIsNull() {
|
||||
UUID orgId = UUID.randomUUID();
|
||||
Organisation o = new Organisation();
|
||||
o.setId(orgId);
|
||||
AgrementProfessionnel entity = AgrementProfessionnel.builder()
|
||||
.membre(null)
|
||||
.organisation(o)
|
||||
.statut(StatutAgrement.VALIDE)
|
||||
.secteurOuOrdre("Droit")
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
AgrementProfessionnelDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getMembreId()).isNull();
|
||||
assertThat(dto.getOrganisationId()).isEqualTo(orgId.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec organisation null produit organisationId null (branche entityOrganisationId — objet null)")
|
||||
void toDto_organisationNull_organisationIdIsNull() {
|
||||
UUID membreId = UUID.randomUUID();
|
||||
Membre m = new Membre();
|
||||
m.setId(membreId);
|
||||
AgrementProfessionnel entity = AgrementProfessionnel.builder()
|
||||
.membre(m)
|
||||
.organisation(null)
|
||||
.statut(StatutAgrement.SUSPENDU)
|
||||
.secteurOuOrdre("Comptabilité")
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
AgrementProfessionnelDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getMembreId()).isEqualTo(membreId.toString());
|
||||
assertThat(dto.getOrganisationId()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto met à jour les champs de l'entité")
|
||||
void updateEntityFromDto_updatesEntity() {
|
||||
AgrementProfessionnel entity = AgrementProfessionnel.builder()
|
||||
.statut(StatutAgrement.PROVISOIRE)
|
||||
.secteurOuOrdre("Ancien secteur")
|
||||
.build();
|
||||
|
||||
AgrementProfessionnelDTO dto = new AgrementProfessionnelDTO();
|
||||
dto.setStatut(StatutAgrement.VALIDE);
|
||||
dto.setSecteurOuOrdre("Nouveau secteur");
|
||||
|
||||
mapper.updateEntityFromDto(dto, entity);
|
||||
|
||||
assertThat(entity.getStatut()).isEqualTo(StatutAgrement.VALIDE);
|
||||
assertThat(entity.getSecteurOuOrdre()).isEqualTo("Nouveau secteur");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité (branche updateEntityFromDto — dto null)")
|
||||
void updateEntityFromDto_dtoNull_entityUnchanged() {
|
||||
AgrementProfessionnel entity = AgrementProfessionnel.builder()
|
||||
.statut(StatutAgrement.VALIDE)
|
||||
.secteurOuOrdre("Secteur initial")
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getStatut()).isEqualTo(StatutAgrement.VALIDE);
|
||||
assertThat(entity.getSecteurOuOrdre()).isEqualTo("Secteur initial");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import dev.lions.unionflow.server.entity.tontine.TourTontine;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -113,4 +116,93 @@ class TontineMapperTest {
|
||||
assertThat(entity.getMontantMiseParTour()).isEqualByComparingTo("15000");
|
||||
assertThat(entity.getLimiteParticipants()).isEqualTo(15);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Branche manquante : updateEntityFromDto — request null → early return (ligne 80)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec request null ne modifie pas l'entité (branche null-guard)")
|
||||
void updateEntityFromDto_requestNull_entityInchangee() {
|
||||
Tontine entity = Tontine.builder()
|
||||
.nom("Invariante")
|
||||
.montantMiseParTour(new BigDecimal("9999"))
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getNom()).isEqualTo("Invariante");
|
||||
assertThat(entity.getMontantMiseParTour()).isEqualByComparingTo("9999");
|
||||
}
|
||||
|
||||
// ==================== Tests: tourTontineListToTourTontineDTOList (branches manquantes) ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec calendrierTours null retourne calendrierTours null dans le DTO")
|
||||
void toDto_calendrierToursNull_returnsDtoWithNullCalendrier() {
|
||||
Tontine entity = new Tontine();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setNom("Tontine sans calendrier");
|
||||
entity.setTypeTontine(TypeTontine.ROTATIVE_CLASSIQUE);
|
||||
entity.setFrequence(FrequenceTour.MENSUELLE);
|
||||
entity.setCalendrierTours(null);
|
||||
|
||||
TontineResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getCalendrierTours()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec calendrierTours vide retourne liste vide dans le DTO")
|
||||
void toDto_calendrierToursVide_returnsDtoWithEmptyCalendrier() {
|
||||
Tontine entity = Tontine.builder()
|
||||
.nom("Tontine vide")
|
||||
.typeTontine(TypeTontine.ROTATIVE_CLASSIQUE)
|
||||
.frequence(FrequenceTour.MENSUELLE)
|
||||
.calendrierTours(Collections.emptyList())
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
TontineResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getCalendrierTours()).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec calendrierTours contenant des tours mappe chaque élément")
|
||||
void toDto_calendrierToursAvecElements_mappeChaqueTour() {
|
||||
TourTontine tour1 = TourTontine.builder()
|
||||
.ordreTour(1)
|
||||
.dateOuvertureCotisations(LocalDate.now())
|
||||
.montantCible(new BigDecimal("50000"))
|
||||
.cagnotteCollectee(BigDecimal.ZERO)
|
||||
.statutInterne("A_VENIR")
|
||||
.build();
|
||||
tour1.setId(UUID.randomUUID());
|
||||
|
||||
TourTontine tour2 = TourTontine.builder()
|
||||
.ordreTour(2)
|
||||
.dateOuvertureCotisations(LocalDate.now().plusMonths(1))
|
||||
.montantCible(new BigDecimal("50000"))
|
||||
.cagnotteCollectee(BigDecimal.ZERO)
|
||||
.statutInterne("A_VENIR")
|
||||
.build();
|
||||
tour2.setId(UUID.randomUUID());
|
||||
|
||||
Tontine entity = new Tontine();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setNom("Tontine avec tours");
|
||||
entity.setTypeTontine(TypeTontine.ROTATIVE_CLASSIQUE);
|
||||
entity.setFrequence(FrequenceTour.MENSUELLE);
|
||||
entity.setCalendrierTours(List.of(tour1, tour2));
|
||||
|
||||
TontineResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getCalendrierTours()).isNotNull().hasSize(2);
|
||||
assertThat(dto.getCalendrierTours().get(0).getOrdreTour()).isEqualTo(1);
|
||||
assertThat(dto.getCalendrierTours().get(1).getOrdreTour()).isEqualTo(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,48 @@ class TourTontineMapperTest {
|
||||
assertThat(dto.getStatutInterne()).isEqualTo("EN_COURS");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec tontine null produit tontineId null (branche entityTontineId — objet null)")
|
||||
void toDto_tontineNull_tontineIdIsNull() {
|
||||
Membre membre = new Membre();
|
||||
membre.setId(UUID.randomUUID());
|
||||
TourTontine entity = TourTontine.builder()
|
||||
.tontine(null)
|
||||
.membreBeneficiaire(membre)
|
||||
.ordreTour(2)
|
||||
.montantCible(new BigDecimal("50000"))
|
||||
.statutInterne("A_VENIR")
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
TourTontineDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getTontineId()).isNull();
|
||||
assertThat(dto.getMembreBeneficiaireId()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec membreBeneficiaire null produit membreBeneficiaireId null (branche entityMembreBeneficiaireId — objet null)")
|
||||
void toDto_membreBeneficiaireNull_membreBeneficiaireIdIsNull() {
|
||||
Tontine tontine = new Tontine();
|
||||
tontine.setId(UUID.randomUUID());
|
||||
TourTontine entity = TourTontine.builder()
|
||||
.tontine(tontine)
|
||||
.membreBeneficiaire(null)
|
||||
.ordreTour(3)
|
||||
.montantCible(new BigDecimal("75000"))
|
||||
.statutInterne("EN_COURS")
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
TourTontineDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getTontineId()).isNotNull();
|
||||
assertThat(dto.getMembreBeneficiaireId()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toEntity mappe DTO vers entity")
|
||||
void toEntity_mapsDtoToEntity() {
|
||||
@@ -97,4 +139,20 @@ class TourTontineMapperTest {
|
||||
assertThat(entity.getOrdreTour()).isEqualTo(3);
|
||||
assertThat(entity.getStatutInterne()).isEqualTo("CLOTURE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité (branche updateEntityFromDto — dto null)")
|
||||
void updateEntityFromDto_dtoNull_entityUnchanged() {
|
||||
TourTontine entity = TourTontine.builder()
|
||||
.ordreTour(5)
|
||||
.statutInterne("EN_COURS")
|
||||
.montantCible(new BigDecimal("999"))
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getOrdreTour()).isEqualTo(5);
|
||||
assertThat(entity.getStatutInterne()).isEqualTo("EN_COURS");
|
||||
assertThat(entity.getMontantCible()).isEqualByComparingTo("999");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,16 @@ import dev.lions.unionflow.server.api.enums.vote.ModeScrutin;
|
||||
import dev.lions.unionflow.server.api.enums.vote.TypeVote;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import dev.lions.unionflow.server.entity.vote.CampagneVote;
|
||||
import dev.lions.unionflow.server.entity.vote.Candidat;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -112,4 +115,92 @@ class CampagneVoteMapperTest {
|
||||
assertThat(entity.getTitre()).isEqualTo("Nouveau titre");
|
||||
assertThat(entity.getTypeVote()).isEqualTo(TypeVote.REFERENDUM);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Branche manquante : updateEntityFromDto — request null → early return (ligne 85)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec request null ne modifie pas l'entité (branche null-guard)")
|
||||
void updateEntityFromDto_requestNull_entityInchangee() {
|
||||
CampagneVote entity = CampagneVote.builder()
|
||||
.titre("Invariant")
|
||||
.typeVote(TypeVote.ELECTION_BUREAU)
|
||||
.modeScrutin(ModeScrutin.MAJORITAIRE_UN_TOUR)
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getTitre()).isEqualTo("Invariant");
|
||||
assertThat(entity.getTypeVote()).isEqualTo(TypeVote.ELECTION_BUREAU);
|
||||
}
|
||||
|
||||
// ==================== Tests: candidatListToCandidatDTOList (branches manquantes) ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec candidats null retourne candidatsExposes null dans le DTO")
|
||||
void toDto_candidatsNull_returnsDtoWithNullCandidats() {
|
||||
CampagneVote entity = new CampagneVote();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setTitre("Vote sans candidats");
|
||||
entity.setTypeVote(TypeVote.REFERENDUM);
|
||||
entity.setModeScrutin(ModeScrutin.MAJORITAIRE_UN_TOUR);
|
||||
entity.setCandidats(null);
|
||||
|
||||
CampagneVoteResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getCandidatsExposes()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec candidats vide retourne liste vide dans le DTO")
|
||||
void toDto_candidatsVide_returnsDtoWithEmptyCandidats() {
|
||||
CampagneVote entity = CampagneVote.builder()
|
||||
.titre("Vote vide")
|
||||
.typeVote(TypeVote.ELECTION_BUREAU)
|
||||
.modeScrutin(ModeScrutin.MAJORITAIRE_UN_TOUR)
|
||||
.candidats(Collections.emptyList())
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
|
||||
CampagneVoteResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getCandidatsExposes()).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec candidats contenant des éléments mappe chaque candidat")
|
||||
void toDto_candidatsAvecElements_mappeChaqueCandidats() {
|
||||
Candidat candidat1 = Candidat.builder()
|
||||
.nomCandidatureOuChoix("Jean Dupont")
|
||||
.professionDeFoi("Pour un renouveau associatif")
|
||||
.nombreDeVoix(0)
|
||||
.pourcentageObtenu(BigDecimal.ZERO)
|
||||
.build();
|
||||
candidat1.setId(UUID.randomUUID());
|
||||
|
||||
Candidat candidat2 = Candidat.builder()
|
||||
.nomCandidatureOuChoix("Marie Martin")
|
||||
.professionDeFoi("Pour la transparence")
|
||||
.nombreDeVoix(0)
|
||||
.pourcentageObtenu(BigDecimal.ZERO)
|
||||
.build();
|
||||
candidat2.setId(UUID.randomUUID());
|
||||
|
||||
CampagneVote entity = new CampagneVote();
|
||||
entity.setId(UUID.randomUUID());
|
||||
entity.setTitre("Élection avec candidats");
|
||||
entity.setTypeVote(TypeVote.ELECTION_BUREAU);
|
||||
entity.setModeScrutin(ModeScrutin.MAJORITAIRE_UN_TOUR);
|
||||
entity.setCandidats(List.of(candidat1, candidat2));
|
||||
|
||||
CampagneVoteResponse dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getCandidatsExposes()).isNotNull().hasSize(2);
|
||||
assertThat(dto.getCandidatsExposes().get(0).getNomCandidatureOuChoix()).isEqualTo("Jean Dupont");
|
||||
assertThat(dto.getCandidatsExposes().get(1).getNomCandidatureOuChoix()).isEqualTo("Marie Martin");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,4 +86,35 @@ class CandidatMapperTest {
|
||||
|
||||
assertThat(entity.getNomCandidatureOuChoix()).isEqualTo("Nouveau choix");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("updateEntityFromDto avec dto null ne modifie pas l'entité")
|
||||
void updateEntityFromDto_nullDto_noOp() {
|
||||
Candidat entity = Candidat.builder()
|
||||
.nomCandidatureOuChoix("Inchangé")
|
||||
.professionDeFoi("Foi inchangée")
|
||||
.build();
|
||||
|
||||
mapper.updateEntityFromDto(null, entity);
|
||||
|
||||
assertThat(entity.getNomCandidatureOuChoix()).isEqualTo("Inchangé");
|
||||
assertThat(entity.getProfessionDeFoi()).isEqualTo("Foi inchangée");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("toDto avec entity sans campagneVote met campagneVoteId à null")
|
||||
void toDto_entityWithNullCampagneVote_nullCampagneVoteId() {
|
||||
Candidat entity = Candidat.builder()
|
||||
.nomCandidatureOuChoix("Sans campagne")
|
||||
.professionDeFoi("Foi")
|
||||
.build();
|
||||
entity.setId(UUID.randomUUID());
|
||||
// campagneVote est null → entityCampagneVoteId retourne null → campagneVoteId non setté
|
||||
|
||||
CandidatDTO dto = mapper.toDto(entity);
|
||||
|
||||
assertThat(dto).isNotNull();
|
||||
assertThat(dto.getCampagneVoteId()).isNull();
|
||||
assertThat(dto.getNomCandidatureOuChoix()).isEqualTo("Sans campagne");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package dev.lions.unionflow.server.messaging;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import dev.lions.unionflow.server.service.WebSocketBroadcastService;
|
||||
import io.quarkus.test.InjectMock;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.smallrye.reactive.messaging.kafka.Record;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests unitaires pour KafkaEventConsumer.
|
||||
*
|
||||
* <p>Les méthodes @Incoming sont appelées directement (sans broker Kafka) ;
|
||||
* WebSocketBroadcastService est mocké via @InjectMock pour isoler le consumer.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
*/
|
||||
@QuarkusTest
|
||||
@DisplayName("KafkaEventConsumer")
|
||||
class KafkaEventConsumerTest {
|
||||
|
||||
@Inject
|
||||
KafkaEventConsumer kafkaEventConsumer;
|
||||
|
||||
@InjectMock
|
||||
WebSocketBroadcastService webSocketBroadcastService;
|
||||
|
||||
@BeforeEach
|
||||
void resetMock() {
|
||||
doNothing().when(webSocketBroadcastService).broadcast(anyString());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// consumeFinanceApprovals
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeFinanceApprovals — broadcast appelé avec la valeur du record")
|
||||
void consumeFinanceApprovals_broadcastAppele() {
|
||||
Record<String, String> record = Record.of("key-finance", "{\"type\":\"APPROVAL_PENDING\"}");
|
||||
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeFinanceApprovals(record))
|
||||
.doesNotThrowAnyException();
|
||||
|
||||
verify(webSocketBroadcastService).broadcast("{\"type\":\"APPROVAL_PENDING\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeFinanceApprovals — exception swallowée (pas de propagation)")
|
||||
void consumeFinanceApprovals_exceptionSwallowee() {
|
||||
Record<String, String> record = Record.of("key-finance", "payload");
|
||||
doThrow(new RuntimeException("WebSocket error")).when(webSocketBroadcastService).broadcast(anyString());
|
||||
|
||||
// L'exception doit être attrapée dans le catch — aucune propagation
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeFinanceApprovals(record))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// consumeDashboardStats
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeDashboardStats — broadcast appelé avec la valeur du record")
|
||||
void consumeDashboardStats_broadcastAppele() {
|
||||
Record<String, String> record = Record.of("key-stats", "{\"totalMembers\":250}");
|
||||
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeDashboardStats(record))
|
||||
.doesNotThrowAnyException();
|
||||
|
||||
verify(webSocketBroadcastService).broadcast("{\"totalMembers\":250}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeDashboardStats — exception swallowée (pas de propagation)")
|
||||
void consumeDashboardStats_exceptionSwallowee() {
|
||||
Record<String, String> record = Record.of("key-stats", "payload");
|
||||
doThrow(new RuntimeException("WebSocket unavailable")).when(webSocketBroadcastService).broadcast(anyString());
|
||||
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeDashboardStats(record))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// consumeNotifications
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeNotifications — broadcast appelé avec la valeur du record")
|
||||
void consumeNotifications_broadcastAppele() {
|
||||
Record<String, String> record = Record.of("key-notif", "{\"message\":\"Cotisation reçue\"}");
|
||||
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeNotifications(record))
|
||||
.doesNotThrowAnyException();
|
||||
|
||||
verify(webSocketBroadcastService).broadcast("{\"message\":\"Cotisation reçue\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeNotifications — exception swallowée (pas de propagation)")
|
||||
void consumeNotifications_exceptionSwallowee() {
|
||||
Record<String, String> record = Record.of("key-notif", "payload");
|
||||
doThrow(new RuntimeException("Broadcast failed")).when(webSocketBroadcastService).broadcast(anyString());
|
||||
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeNotifications(record))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// consumeMembersEvents
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeMembersEvents — broadcast appelé avec la valeur du record")
|
||||
void consumeMembersEvents_broadcastAppele() {
|
||||
Record<String, String> record = Record.of("key-member", "{\"action\":\"MEMBER_CREATED\"}");
|
||||
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeMembersEvents(record))
|
||||
.doesNotThrowAnyException();
|
||||
|
||||
verify(webSocketBroadcastService).broadcast("{\"action\":\"MEMBER_CREATED\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeMembersEvents — exception swallowée (pas de propagation)")
|
||||
void consumeMembersEvents_exceptionSwallowee() {
|
||||
Record<String, String> record = Record.of("key-member", "payload");
|
||||
doThrow(new RuntimeException("Connection lost")).when(webSocketBroadcastService).broadcast(anyString());
|
||||
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeMembersEvents(record))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// consumeContributionsEvents
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeContributionsEvents — broadcast appelé avec la valeur du record")
|
||||
void consumeContributionsEvents_broadcastAppele() {
|
||||
Record<String, String> record = Record.of("key-contrib", "{\"action\":\"CONTRIBUTION_PAID\"}");
|
||||
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeContributionsEvents(record))
|
||||
.doesNotThrowAnyException();
|
||||
|
||||
verify(webSocketBroadcastService).broadcast("{\"action\":\"CONTRIBUTION_PAID\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("consumeContributionsEvents — exception swallowée (pas de propagation)")
|
||||
void consumeContributionsEvents_exceptionSwallowee() {
|
||||
Record<String, String> record = Record.of("key-contrib", "payload");
|
||||
doThrow(new RuntimeException("Timeout")).when(webSocketBroadcastService).broadcast(anyString());
|
||||
|
||||
assertThatCode(() -> kafkaEventConsumer.consumeContributionsEvents(record))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package dev.lions.unionflow.server.messaging;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import io.smallrye.reactive.messaging.kafka.Record;
|
||||
import org.eclipse.microprofile.reactive.messaging.Emitter;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tests de couverture pour la méthode privée {@code KafkaEventProducer.publishToChannel}.
|
||||
*
|
||||
* <p>Utilise l'instanciation directe + réflexion pour éviter les limitations de CDI
|
||||
* ({@code @InjectMock} ne supporte pas les beans {@code @Singleton} comme ObjectMapper).
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("KafkaEventProducer - publishToChannel branches catch")
|
||||
class KafkaEventProducerPublishToChannelTest {
|
||||
|
||||
private KafkaEventProducer producer;
|
||||
private ObjectMapper mockObjectMapper;
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("unchecked")
|
||||
void setup() throws Exception {
|
||||
producer = new KafkaEventProducer();
|
||||
mockObjectMapper = mock(ObjectMapper.class);
|
||||
|
||||
// Injecter le mock ObjectMapper dans l'instance directe
|
||||
Field omField = KafkaEventProducer.class.getDeclaredField("objectMapper");
|
||||
omField.setAccessible(true);
|
||||
omField.set(producer, mockObjectMapper);
|
||||
}
|
||||
|
||||
/** Invoque publishToChannel via réflexion sur l'instance directe avec un mock Emitter. */
|
||||
@SuppressWarnings("unchecked")
|
||||
private void invokePublishToChannel(Emitter<Record<String, String>> emitter,
|
||||
String key, Map<String, Object> event,
|
||||
String topicName) throws Exception {
|
||||
Method method = KafkaEventProducer.class.getDeclaredMethod(
|
||||
"publishToChannel", Emitter.class, String.class, Map.class, String.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(producer, emitter, key, event, topicName);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// catch (JsonProcessingException e) — lignes 149-150
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("publishToChannel - JsonProcessingException → loggée sans relancer (branche catch L149-150)")
|
||||
void publishToChannel_jsonProcessingException_loggeeEtNonPropagee() throws Exception {
|
||||
when(mockObjectMapper.writeValueAsString(any()))
|
||||
.thenThrow(new JsonProcessingException("Erreur sérialisation simulée") {});
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Emitter<Record<String, String>> mockEmitter = mock(Emitter.class);
|
||||
|
||||
assertThatCode(() -> invokePublishToChannel(mockEmitter, "key", new HashMap<>(), "finance-approvals"))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishToChannel - JsonProcessingException sur topic notifications → loggée sans relancer")
|
||||
void publishToChannel_jsonProcessingException_topicNotifications() throws Exception {
|
||||
when(mockObjectMapper.writeValueAsString(any()))
|
||||
.thenThrow(new JsonProcessingException("Serialisation notification échouée") {});
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Emitter<Record<String, String>> mockEmitter = mock(Emitter.class);
|
||||
|
||||
assertThatCode(() -> invokePublishToChannel(mockEmitter, "user-id", new HashMap<>(), "notifications"))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// catch (Exception e) générique — lignes 151-152
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("publishToChannel - RuntimeException depuis emitter → loggée sans relancer (branche catch L151-152)")
|
||||
void publishToChannel_exceptionGenerique_loggeeEtNonPropagee() throws Exception {
|
||||
when(mockObjectMapper.writeValueAsString(any())).thenReturn("{\"type\":\"test\"}");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Emitter<Record<String, String>> mockEmitter = mock(Emitter.class);
|
||||
doThrow(new RuntimeException("Erreur émetteur simulée")).when(mockEmitter).send(ArgumentMatchers.<Record<String, String>>any());
|
||||
|
||||
assertThatCode(() -> invokePublishToChannel(mockEmitter, "key", new HashMap<>(), "dashboard-stats"))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishToChannel - IllegalStateException depuis emitter → loggée sans relancer")
|
||||
void publishToChannel_exceptionGenerique_topicMembersEvents() throws Exception {
|
||||
when(mockObjectMapper.writeValueAsString(any())).thenReturn("{\"type\":\"member\"}");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Emitter<Record<String, String>> mockEmitter = mock(Emitter.class);
|
||||
doThrow(new IllegalStateException("État emitter invalide")).when(mockEmitter).send(ArgumentMatchers.<Record<String, String>>any());
|
||||
|
||||
assertThatCode(() -> invokePublishToChannel(mockEmitter, "member-id", new HashMap<>(), "members-events"))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package dev.lions.unionflow.server.messaging;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Tests d'intégration pour KafkaEventProducer.
|
||||
*
|
||||
* <p>Utilise le connecteur in-memory SmallRye (configuré dans application-test.properties)
|
||||
* pour éviter toute dépendance à un broker Kafka réel.
|
||||
* Vérifie que chaque méthode de publication ne lève pas d'exception.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
*/
|
||||
@QuarkusTest
|
||||
@DisplayName("KafkaEventProducer")
|
||||
class KafkaEventProducerTest {
|
||||
|
||||
@Inject
|
||||
KafkaEventProducer kafkaEventProducer;
|
||||
|
||||
private static final UUID APPROVAL_ID = UUID.randomUUID();
|
||||
private static final UUID MEMBER_ID = UUID.randomUUID();
|
||||
private static final UUID CONTRIBUTION_ID = UUID.randomUUID();
|
||||
private static final String ORG_ID = UUID.randomUUID().toString();
|
||||
private static final String USER_ID = UUID.randomUUID().toString();
|
||||
|
||||
private Map<String, Object> sampleData() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("montant", 150000);
|
||||
data.put("devise", "XOF");
|
||||
data.put("statut", "EN_ATTENTE");
|
||||
return data;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Injection
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("KafkaEventProducer est injectable et non null")
|
||||
void producerIsInjected() {
|
||||
assertThat(kafkaEventProducer).isNotNull();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Finance approvals
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("publishApprovalPending - ne leve pas d'exception")
|
||||
void publishApprovalPending_sansException() {
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishApprovalPending(APPROVAL_ID, ORG_ID, sampleData())
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishApprovalPending - data null - ne leve pas d'exception")
|
||||
void publishApprovalPending_dataNul_sansException() {
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishApprovalPending(APPROVAL_ID, ORG_ID, null)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishApprovalApproved - ne leve pas d'exception")
|
||||
void publishApprovalApproved_sansException() {
|
||||
Map<String, Object> data = sampleData();
|
||||
data.put("approbateur", "admin@test.com");
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishApprovalApproved(APPROVAL_ID, ORG_ID, data)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishApprovalRejected - ne leve pas d'exception")
|
||||
void publishApprovalRejected_sansException() {
|
||||
Map<String, Object> data = sampleData();
|
||||
data.put("motif", "Documents manquants");
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishApprovalRejected(APPROVAL_ID, ORG_ID, data)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishApprovalRejected - organizationId null - ne leve pas d'exception")
|
||||
void publishApprovalRejected_organizationIdNull_sansException() {
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishApprovalRejected(APPROVAL_ID, null, sampleData())
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Dashboard stats
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("publishDashboardStatsUpdate - ne leve pas d'exception")
|
||||
void publishDashboardStatsUpdate_sansException() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalMembres", 250);
|
||||
stats.put("totalCotisations", 12500000);
|
||||
stats.put("tauxRecouvrement", 0.85);
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishDashboardStatsUpdate(ORG_ID, stats)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishKpiUpdate - ne leve pas d'exception")
|
||||
void publishKpiUpdate_sansException() {
|
||||
Map<String, Object> kpiData = new HashMap<>();
|
||||
kpiData.put("kpi", "TAUX_ADHESION");
|
||||
kpiData.put("valeur", 0.92);
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishKpiUpdate(ORG_ID, kpiData)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishKpiUpdate - data vide - ne leve pas d'exception")
|
||||
void publishKpiUpdate_dataVide_sansException() {
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishKpiUpdate(ORG_ID, new HashMap<>())
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Notifications
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("publishUserNotification - ne leve pas d'exception")
|
||||
void publishUserNotification_sansException() {
|
||||
Map<String, Object> notif = new HashMap<>();
|
||||
notif.put("titre", "Paiement recu");
|
||||
notif.put("message", "Votre cotisation de 5000 XOF a ete enregistree.");
|
||||
notif.put("type", "COTISATION");
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishUserNotification(USER_ID, notif)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishUserNotification - payload minimal - ne leve pas d'exception")
|
||||
void publishUserNotification_payloadMinimal_sansException() {
|
||||
Map<String, Object> notif = new HashMap<>();
|
||||
notif.put("message", "Test");
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishUserNotification(USER_ID, notif)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishBroadcastNotification - ne leve pas d'exception")
|
||||
void publishBroadcastNotification_sansException() {
|
||||
Map<String, Object> notif = new HashMap<>();
|
||||
notif.put("titre", "Reunion annuelle");
|
||||
notif.put("message", "La reunion annuelle est prevue le 15 mars 2026");
|
||||
notif.put("type", "EVENEMENT");
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishBroadcastNotification(ORG_ID, notif)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Members events
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("publishMemberCreated - ne leve pas d'exception")
|
||||
void publishMemberCreated_sansException() {
|
||||
Map<String, Object> memberData = new HashMap<>();
|
||||
memberData.put("nom", "Diallo");
|
||||
memberData.put("prenom", "Amadou");
|
||||
memberData.put("email", "amadou.diallo@test.com");
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishMemberCreated(MEMBER_ID, ORG_ID, memberData)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishMemberUpdated - ne leve pas d'exception")
|
||||
void publishMemberUpdated_sansException() {
|
||||
Map<String, Object> memberData = new HashMap<>();
|
||||
memberData.put("telephone", "+2250700000001");
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishMemberUpdated(MEMBER_ID, ORG_ID, memberData)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishMemberUpdated - organizationId null - ne leve pas d'exception")
|
||||
void publishMemberUpdated_organizationIdNull_sansException() {
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishMemberUpdated(MEMBER_ID, null, sampleData())
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Contributions events
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName("publishContributionPaid - ne leve pas d'exception")
|
||||
void publishContributionPaid_sansException() {
|
||||
Map<String, Object> contributionData = new HashMap<>();
|
||||
contributionData.put("montant", 5000);
|
||||
contributionData.put("devise", "XOF");
|
||||
contributionData.put("periode", "2026-03");
|
||||
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishContributionPaid(CONTRIBUTION_ID, ORG_ID, contributionData)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("publishContributionPaid - data null - ne leve pas d'exception")
|
||||
void publishContributionPaid_dataNul_sansException() {
|
||||
assertThatCode(() ->
|
||||
kafkaEventProducer.publishContributionPaid(CONTRIBUTION_ID, ORG_ID, null)
|
||||
).doesNotThrowAnyException();
|
||||
}
|
||||
}
|
||||
@@ -91,4 +91,41 @@ class AdresseRepositoryTest {
|
||||
long count = adresseRepository.count();
|
||||
assertThat((long) all.size()).isEqualTo(count);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByType retourne les adresses du type demandé")
|
||||
void findByType_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Adresse a = newAdresse(org);
|
||||
adresseRepository.persist(a);
|
||||
List<Adresse> list = adresseRepository.findByType("SIEGE");
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
assertThat(list).allMatch(addr -> "SIEGE".equals(addr.getTypeAdresse()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByVille retourne les adresses de la ville demandée")
|
||||
void findByVille_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Adresse a = newAdresse(org);
|
||||
adresseRepository.persist(a);
|
||||
List<Adresse> list = adresseRepository.findByVille("Paris");
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByPays retourne les adresses du pays demandé")
|
||||
void findByPays_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Adresse a = newAdresse(org);
|
||||
adresseRepository.persist(a);
|
||||
List<Adresse> list = adresseRepository.findByPays("France");
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.AlertConfiguration;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests d'intégration pour AlertConfigurationRepository.
|
||||
*
|
||||
* <p>Couvre les méthodes : getConfiguration (existing + create), updateConfiguration,
|
||||
* isCpuAlertEnabled, isMemoryAlertEnabled, getCpuThreshold, getMemoryThreshold.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-21
|
||||
*/
|
||||
@QuarkusTest
|
||||
class AlertConfigurationRepositoryTest {
|
||||
|
||||
@Inject
|
||||
AlertConfigurationRepository alertConfigurationRepository;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getConfiguration — création par défaut (branche else, lignes 42-45)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("getConfiguration crée une configuration par défaut si aucune n'existe")
|
||||
void getConfiguration_noExisting_createsDefault() {
|
||||
// Supprimer toutes les configurations existantes pour tester la branche création
|
||||
alertConfigurationRepository.listAll().forEach(alertConfigurationRepository::delete);
|
||||
|
||||
AlertConfiguration config = alertConfigurationRepository.getConfiguration();
|
||||
|
||||
assertThat(config).isNotNull();
|
||||
assertThat(config.getId()).isNotNull();
|
||||
// Valeurs par défaut de l'entité
|
||||
assertThat(config.getCpuHighAlertEnabled()).isTrue();
|
||||
assertThat(config.getCpuThresholdPercent()).isEqualTo(80);
|
||||
assertThat(config.getMemoryLowAlertEnabled()).isTrue();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getConfiguration — récupération existante (branche if, ligne 39)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("getConfiguration retourne la configuration existante (branche présente)")
|
||||
void getConfiguration_existing_returnsExisting() {
|
||||
// S'assurer qu'une configuration existe
|
||||
AlertConfiguration first = alertConfigurationRepository.getConfiguration();
|
||||
assertThat(first).isNotNull();
|
||||
first.setCpuThresholdPercent(75);
|
||||
alertConfigurationRepository.persist(first);
|
||||
|
||||
// Deuxième appel : doit retourner la configuration existante (branche if, ligne 39)
|
||||
AlertConfiguration second = alertConfigurationRepository.getConfiguration();
|
||||
assertThat(second).isNotNull();
|
||||
// Doit retourner la même configuration (ou au moins une valeur persistée)
|
||||
assertThat(second.getCpuThresholdPercent()).isEqualTo(75);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// updateConfiguration (lignes 52-71)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("updateConfiguration met à jour tous les champs de la configuration")
|
||||
void updateConfiguration_updatesAllFields() {
|
||||
// S'assurer qu'une config de base existe
|
||||
alertConfigurationRepository.getConfiguration();
|
||||
|
||||
AlertConfiguration update = new AlertConfiguration();
|
||||
update.setCpuHighAlertEnabled(false);
|
||||
update.setCpuThresholdPercent(90);
|
||||
update.setCpuDurationMinutes(10);
|
||||
update.setMemoryLowAlertEnabled(false);
|
||||
update.setMemoryThresholdPercent(70);
|
||||
update.setCriticalErrorAlertEnabled(false);
|
||||
update.setErrorAlertEnabled(false);
|
||||
update.setConnectionFailureAlertEnabled(false);
|
||||
update.setConnectionFailureThreshold(50);
|
||||
update.setConnectionFailureWindowMinutes(15);
|
||||
update.setEmailNotificationsEnabled(false);
|
||||
update.setPushNotificationsEnabled(true);
|
||||
update.setSmsNotificationsEnabled(true);
|
||||
update.setAlertEmailRecipients("new@unionflow.test,admin@unionflow.test");
|
||||
|
||||
AlertConfiguration result = alertConfigurationRepository.updateConfiguration(update);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getCpuHighAlertEnabled()).isFalse();
|
||||
assertThat(result.getCpuThresholdPercent()).isEqualTo(90);
|
||||
assertThat(result.getCpuDurationMinutes()).isEqualTo(10);
|
||||
assertThat(result.getMemoryLowAlertEnabled()).isFalse();
|
||||
assertThat(result.getMemoryThresholdPercent()).isEqualTo(70);
|
||||
assertThat(result.getCriticalErrorAlertEnabled()).isFalse();
|
||||
assertThat(result.getErrorAlertEnabled()).isFalse();
|
||||
assertThat(result.getConnectionFailureAlertEnabled()).isFalse();
|
||||
assertThat(result.getConnectionFailureThreshold()).isEqualTo(50);
|
||||
assertThat(result.getConnectionFailureWindowMinutes()).isEqualTo(15);
|
||||
assertThat(result.getEmailNotificationsEnabled()).isFalse();
|
||||
assertThat(result.getPushNotificationsEnabled()).isTrue();
|
||||
assertThat(result.getSmsNotificationsEnabled()).isTrue();
|
||||
assertThat(result.getAlertEmailRecipients()).isEqualTo("new@unionflow.test,admin@unionflow.test");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isCpuAlertEnabled (ligne 78)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isCpuAlertEnabled retourne l'état d'activation de l'alerte CPU")
|
||||
void isCpuAlertEnabled_returnsConfigValue() {
|
||||
// S'assurer qu'une configuration existe avec une valeur connue
|
||||
AlertConfiguration config = alertConfigurationRepository.getConfiguration();
|
||||
boolean expected = config.getCpuHighAlertEnabled();
|
||||
|
||||
boolean result = alertConfigurationRepository.isCpuAlertEnabled();
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isMemoryAlertEnabled (ligne 85)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isMemoryAlertEnabled retourne l'état d'activation de l'alerte mémoire")
|
||||
void isMemoryAlertEnabled_returnsConfigValue() {
|
||||
AlertConfiguration config = alertConfigurationRepository.getConfiguration();
|
||||
boolean expected = config.getMemoryLowAlertEnabled();
|
||||
|
||||
boolean result = alertConfigurationRepository.isMemoryAlertEnabled();
|
||||
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getCpuThreshold (ligne 92)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("getCpuThreshold retourne le seuil CPU configuré")
|
||||
void getCpuThreshold_returnsConfiguredThreshold() {
|
||||
// Configurer un seuil spécifique
|
||||
AlertConfiguration update = new AlertConfiguration();
|
||||
update.setCpuHighAlertEnabled(true);
|
||||
update.setCpuThresholdPercent(65);
|
||||
update.setCpuDurationMinutes(5);
|
||||
update.setMemoryLowAlertEnabled(true);
|
||||
update.setMemoryThresholdPercent(85);
|
||||
update.setCriticalErrorAlertEnabled(true);
|
||||
update.setErrorAlertEnabled(true);
|
||||
update.setConnectionFailureAlertEnabled(true);
|
||||
update.setConnectionFailureThreshold(100);
|
||||
update.setConnectionFailureWindowMinutes(5);
|
||||
update.setEmailNotificationsEnabled(true);
|
||||
update.setPushNotificationsEnabled(false);
|
||||
update.setSmsNotificationsEnabled(false);
|
||||
update.setAlertEmailRecipients("admin@unionflow.test");
|
||||
alertConfigurationRepository.updateConfiguration(update);
|
||||
|
||||
int threshold = alertConfigurationRepository.getCpuThreshold();
|
||||
|
||||
assertThat(threshold).isEqualTo(65);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getMemoryThreshold (ligne 99)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("getMemoryThreshold retourne le seuil mémoire configuré")
|
||||
void getMemoryThreshold_returnsConfiguredThreshold() {
|
||||
AlertConfiguration update = new AlertConfiguration();
|
||||
update.setCpuHighAlertEnabled(true);
|
||||
update.setCpuThresholdPercent(80);
|
||||
update.setCpuDurationMinutes(5);
|
||||
update.setMemoryLowAlertEnabled(true);
|
||||
update.setMemoryThresholdPercent(72);
|
||||
update.setCriticalErrorAlertEnabled(true);
|
||||
update.setErrorAlertEnabled(true);
|
||||
update.setConnectionFailureAlertEnabled(true);
|
||||
update.setConnectionFailureThreshold(100);
|
||||
update.setConnectionFailureWindowMinutes(5);
|
||||
update.setEmailNotificationsEnabled(true);
|
||||
update.setPushNotificationsEnabled(false);
|
||||
update.setSmsNotificationsEnabled(false);
|
||||
update.setAlertEmailRecipients("admin@unionflow.test");
|
||||
alertConfigurationRepository.updateConfiguration(update);
|
||||
|
||||
int threshold = alertConfigurationRepository.getMemoryThreshold();
|
||||
|
||||
assertThat(threshold).isEqualTo(72);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.AlerteLcbFt;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@QuarkusTest
|
||||
class AlerteLcbFtRepositoryTest {
|
||||
|
||||
@Inject
|
||||
AlerteLcbFtRepository alerteLcbFtRepository;
|
||||
@Inject
|
||||
OrganisationRepository organisationRepository;
|
||||
@Inject
|
||||
MembreRepository membreRepository;
|
||||
|
||||
private Organisation newOrganisation() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("Org AlerteLcbFt");
|
||||
o.setTypeOrganisation("ASSOCIATION");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("alerte-org-" + UUID.randomUUID() + "@test.com");
|
||||
o.setActif(true);
|
||||
organisationRepository.persist(o);
|
||||
return o;
|
||||
}
|
||||
|
||||
private Membre newMembre() {
|
||||
Membre m = new Membre();
|
||||
m.setNumeroMembre("AL-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
m.setPrenom("Test");
|
||||
m.setNom("User");
|
||||
m.setEmail("alerte-mb-" + UUID.randomUUID() + "@test.com");
|
||||
m.setDateNaissance(LocalDate.of(1985, 6, 15));
|
||||
m.setActif(true);
|
||||
membreRepository.persist(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
private AlerteLcbFt newAlerte(Organisation org, Membre membre, String typeAlerte, boolean traitee) {
|
||||
AlerteLcbFt a = AlerteLcbFt.builder()
|
||||
.organisation(org)
|
||||
.membre(membre)
|
||||
.typeAlerte(typeAlerte)
|
||||
.dateAlerte(LocalDateTime.now())
|
||||
.description("Alerte de test " + UUID.randomUUID().toString().substring(0, 8))
|
||||
.montant(BigDecimal.valueOf(1500000))
|
||||
.seuil(BigDecimal.valueOf(1000000))
|
||||
.severite("WARNING")
|
||||
.traitee(traitee)
|
||||
.typeOperation("DEPOT")
|
||||
.transactionRef(UUID.randomUUID().toString())
|
||||
.build();
|
||||
a.setActif(true);
|
||||
alerteLcbFtRepository.persist(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("persist puis findById retrouve l'alerte")
|
||||
void persist_thenFindById_findsAlerte() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
AlerteLcbFt a = newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
assertThat(a.getId()).isNotNull();
|
||||
AlerteLcbFt found = alerteLcbFtRepository.findById(a.getId());
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getTypeAlerte()).isEqualTo("SEUIL_DEPASSE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search sans filtres retourne une liste")
|
||||
void search_noFilters_returnsList() {
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(null, null, null, null, null, 0, 10);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search avec filtre organisationId retourne les alertes de l'organisation")
|
||||
void search_withOrganisationId_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(org.getId(), null, null, null, null, 0, 10);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).hasSizeGreaterThanOrEqualTo(1);
|
||||
assertThat(list).allMatch(a -> a.getOrganisation().getId().equals(org.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search avec filtre typeAlerte retourne les alertes du bon type")
|
||||
void search_withTypeAlerte_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "JUSTIFICATION_MANQUANTE", false);
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(
|
||||
null, "JUSTIFICATION_MANQUANTE", null, null, null, 0, 10);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).hasSizeGreaterThanOrEqualTo(1);
|
||||
assertThat(list).allMatch(a -> "JUSTIFICATION_MANQUANTE".equals(a.getTypeAlerte()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search avec filtre traitee=false retourne les alertes non traitées")
|
||||
void search_withTraiteeFalse_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(
|
||||
org.getId(), null, false, null, null, 0, 10);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).hasSizeGreaterThanOrEqualTo(1);
|
||||
assertThat(list).allMatch(a -> Boolean.FALSE.equals(a.getTraitee()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search avec filtre traitee=true retourne les alertes traitées")
|
||||
void search_withTraiteeTrue_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", true);
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(
|
||||
org.getId(), null, true, null, null, 0, 10);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).allMatch(a -> Boolean.TRUE.equals(a.getTraitee()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search avec dateDebut retourne les alertes depuis la date")
|
||||
void search_withDateDebut_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
LocalDateTime dateDebut = LocalDateTime.now().minusDays(1);
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(
|
||||
org.getId(), null, null, dateDebut, null, 0, 10);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).hasSizeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search avec dateFin retourne les alertes jusqu'à la date")
|
||||
void search_withDateFin_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
LocalDateTime dateFin = LocalDateTime.now().plusDays(1);
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(
|
||||
org.getId(), null, null, null, dateFin, 0, 10);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).hasSizeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search avec tous les filtres retourne liste filtrée")
|
||||
void search_withAllFilters_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
LocalDateTime debut = LocalDateTime.now().minusHours(1);
|
||||
LocalDateTime fin = LocalDateTime.now().plusHours(1);
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(
|
||||
org.getId(), "SEUIL_DEPASSE", false, debut, fin, 0, 10);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).hasSizeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
// ── Branch coverage manquantes ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* L39 + L63 branch manquante (search) : typeAlerte != null mais isBlank() → false
|
||||
* → `if (typeAlerte != null && !typeAlerte.isBlank())` → false (typeAlerte est " ")
|
||||
*/
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search avec typeAlerte blank couvre la branche isBlank (ignoré comme filtre)")
|
||||
void search_withBlankTypeAlerte_treatedAsNoFilter() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
|
||||
// typeAlerte = " " → !typeAlerte.isBlank() est false → branche false → ignoré
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(org.getId(), " ", null, null, null, 0, 10);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("search avec pagination page 1 retourne liste")
|
||||
void search_secondPage_returnsList() {
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.search(null, null, null, null, null, 1, 5);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("count sans filtres retourne un nombre >= 0")
|
||||
void count_noFilters_returnsNonNegative() {
|
||||
long count = alerteLcbFtRepository.count(null, null, null, null, null);
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("count avec filtre organisationId retourne le bon compte")
|
||||
void count_withOrganisationId_returnsCount() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
long count = alerteLcbFtRepository.count(org.getId(), null, null, null, null);
|
||||
assertThat(count).isGreaterThanOrEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("count avec filtre typeAlerte retourne le bon compte")
|
||||
void count_withTypeAlerte_returnsCount() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
long count = alerteLcbFtRepository.count(null, "SEUIL_DEPASSE", null, null, null);
|
||||
assertThat(count).isGreaterThanOrEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("count avec filtre traitee retourne le bon compte")
|
||||
void count_withTraitee_returnsCount() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
long count = alerteLcbFtRepository.count(org.getId(), null, false, null, null);
|
||||
assertThat(count).isGreaterThanOrEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("count avec filtre dateDebut et dateFin retourne le bon compte")
|
||||
void count_withDateRange_returnsCount() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
LocalDateTime debut = LocalDateTime.now().minusDays(1);
|
||||
LocalDateTime fin = LocalDateTime.now().plusDays(1);
|
||||
long count = alerteLcbFtRepository.count(org.getId(), null, null, debut, fin);
|
||||
assertThat(count).isGreaterThanOrEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("count avec tous les filtres retourne le bon compte")
|
||||
void count_withAllFilters_returnsCount() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
LocalDateTime debut = LocalDateTime.now().minusHours(1);
|
||||
LocalDateTime fin = LocalDateTime.now().plusHours(1);
|
||||
long count = alerteLcbFtRepository.count(org.getId(), "SEUIL_DEPASSE", false, debut, fin);
|
||||
assertThat(count).isGreaterThanOrEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countNonTraitees avec null retourne le total des alertes non traitées")
|
||||
void countNonTraitees_null_returnsTotalNonTraitees() {
|
||||
long count = alerteLcbFtRepository.countNonTraitees(null);
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countNonTraitees pour une organisation retourne le bon compte")
|
||||
void countNonTraitees_withOrganisationId_returnsCount() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
long count = alerteLcbFtRepository.countNonTraitees(org.getId());
|
||||
assertThat(count).isGreaterThanOrEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countNonTraitees pour organisation sans alertes retourne 0")
|
||||
void countNonTraitees_noAlerts_returnsZero() {
|
||||
Organisation org = newOrganisation();
|
||||
long count = alerteLcbFtRepository.countNonTraitees(org.getId());
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countNonTraitees après traitement de toutes les alertes retourne 0")
|
||||
void countNonTraitees_allTreated_returnsZero() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", true);
|
||||
long count = alerteLcbFtRepository.countNonTraitees(org.getId());
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* L101 + L123 branch manquante (count) : typeAlerte != null mais isBlank() → false
|
||||
* → `if (typeAlerte != null && !typeAlerte.isBlank())` → false (typeAlerte est " ")
|
||||
*/
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("count avec typeAlerte blank couvre la branche isBlank (ignoré comme filtre)")
|
||||
void count_withBlankTypeAlerte_treatedAsNoFilter() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
newAlerte(org, membre, "SEUIL_DEPASSE", false);
|
||||
|
||||
// typeAlerte = " " → !typeAlerte.isBlank() est false → branche false → ignoré
|
||||
long count = alerteLcbFtRepository.count(org.getId(), " ", null, null, null);
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("count global retourne un nombre >= 0")
|
||||
void countAll_returnsNonNegative() {
|
||||
long count = alerteLcbFtRepository.count();
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("listAll retourne une liste")
|
||||
void listAll_returnsList() {
|
||||
List<AlerteLcbFt> list = alerteLcbFtRepository.listAll();
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests couvrant les méthodes de BaseRepository via OrganisationRepository (qui l'étend).
|
||||
*/
|
||||
@QuarkusTest
|
||||
class BaseRepositoryDirectTest {
|
||||
|
||||
@Inject
|
||||
OrganisationRepository repo;
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByIdOptional retourne empty pour UUID inexistant")
|
||||
void findByIdOptional_inexistant_returnsEmpty() {
|
||||
Optional<Organisation> opt = repo.findByIdOptional(UUID.randomUUID());
|
||||
assertThat(opt).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("listAll retourne une liste non-null")
|
||||
void listAll_returnsNonNull() {
|
||||
List<Organisation> list = repo.listAll();
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("count retourne un nombre >= 0")
|
||||
void count_returnsNonNegative() {
|
||||
long n = repo.count();
|
||||
assertThat(n).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("getEntityManager retourne un EntityManager non-null")
|
||||
void getEntityManager_returnsNonNull() {
|
||||
EntityManager em = repo.getEntityManager();
|
||||
assertThat(em).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("deleteById sur UUID inexistant retourne false")
|
||||
void deleteById_inexistant_returnsFalse() {
|
||||
boolean deleted = repo.deleteById(UUID.randomUUID());
|
||||
assertThat(deleted).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("existsById retourne false pour UUID inexistant")
|
||||
void existsById_inexistant_returnsFalse() {
|
||||
boolean exists = repo.existsById(UUID.randomUUID());
|
||||
assertThat(exists).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findAll avec page et sort null retourne une liste")
|
||||
void findAll_withPageAndNullSort_returnsList() {
|
||||
List<Organisation> list = repo.findAll(Page.of(0, 10), null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findAll avec page et sort non-null retourne une liste")
|
||||
void findAll_withPageAndSort_returnsList() {
|
||||
List<Organisation> list = repo.findAll(Page.of(0, 10), Sort.by("nom", Sort.Direction.Ascending));
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("persist puis findById puis deleteById puis findById retourne null")
|
||||
void persist_then_deleteById_returnsTrue() {
|
||||
Organisation org = new Organisation();
|
||||
org.setNom("Base Repo Test " + UUID.randomUUID());
|
||||
org.setEmail("base-repo-" + UUID.randomUUID() + "@test.com");
|
||||
org.setTypeOrganisation("ASSOCIATION");
|
||||
org.setStatut("ACTIVE");
|
||||
org.setActif(true);
|
||||
|
||||
repo.persist(org);
|
||||
assertThat(org.getId()).isNotNull();
|
||||
|
||||
boolean deleted = repo.deleteById(org.getId());
|
||||
assertThat(deleted).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("update retourne l'entité mise à jour")
|
||||
void update_returnsUpdatedEntity() {
|
||||
Organisation org = new Organisation();
|
||||
org.setNom("Update Test " + UUID.randomUUID());
|
||||
org.setEmail("update-" + UUID.randomUUID() + "@test.com");
|
||||
org.setTypeOrganisation("ASSOCIATION");
|
||||
org.setStatut("ACTIVE");
|
||||
org.setActif(true);
|
||||
|
||||
repo.persist(org);
|
||||
org.setNom("Nom Modifie");
|
||||
Organisation updated = repo.update(org);
|
||||
assertThat(updated).isNotNull();
|
||||
assertThat(updated.getNom()).isEqualTo("Nom Modifie");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("delete sur entité persistée la supprime")
|
||||
void delete_persistedEntity_removesIt() {
|
||||
Organisation org = new Organisation();
|
||||
org.setNom("Delete Test " + UUID.randomUUID());
|
||||
org.setEmail("delete-" + UUID.randomUUID() + "@test.com");
|
||||
org.setTypeOrganisation("ASSOCIATION");
|
||||
org.setStatut("ACTIVE");
|
||||
org.setActif(true);
|
||||
|
||||
repo.persist(org);
|
||||
UUID id = org.getId();
|
||||
assertThat(id).isNotNull();
|
||||
|
||||
repo.delete(org);
|
||||
Organisation found = repo.findById(id);
|
||||
assertThat(found).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("persist avec ID déjà défini utilise merge")
|
||||
void persist_withExistingId_usesMerge() {
|
||||
Organisation org = new Organisation();
|
||||
org.setNom("Merge Test " + UUID.randomUUID());
|
||||
org.setEmail("merge-" + UUID.randomUUID() + "@test.com");
|
||||
org.setTypeOrganisation("ASSOCIATION");
|
||||
org.setStatut("ACTIVE");
|
||||
org.setActif(true);
|
||||
|
||||
repo.persist(org);
|
||||
UUID id = org.getId();
|
||||
|
||||
org.setNom("Nom Apres Merge");
|
||||
repo.persist(org); // doit utiliser merge car id != null
|
||||
|
||||
Organisation found = repo.findById(id);
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getNom()).isEqualTo("Nom Apres Merge");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("delete null ne lance pas d'exception")
|
||||
void delete_null_noException() {
|
||||
// delete(null) doit être géré sans NPE
|
||||
repo.delete(null);
|
||||
}
|
||||
}
|
||||
@@ -177,4 +177,25 @@ class BaseRepositoryTest {
|
||||
void getEntityManager_returnsNonNull() {
|
||||
assertThat(organisationRepository.getEntityManager()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("persist avec entité ayant un id existant effectue un merge")
|
||||
void persist_withExistingId_performsMerge() {
|
||||
Organisation o = newOrganisation("merge-" + UUID.randomUUID() + "@test.com");
|
||||
organisationRepository.persist(o);
|
||||
UUID id = o.getId();
|
||||
o.setNom("Nom après merge");
|
||||
organisationRepository.persist(o);
|
||||
Organisation found = organisationRepository.findById(id);
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getNom()).isEqualTo("Nom après merge");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("delete avec entité null ne lève pas d'exception")
|
||||
void delete_null_doesNotThrow() {
|
||||
organisationRepository.delete(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheQuery;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests unitaires directs pour les méthodes de BaseRepository dont
|
||||
* Quarkus génère des implémentations alternatives dans les sous-classes concrètes
|
||||
* (findByIdOptional, deleteById, listAll, count, getEntityManager).
|
||||
* Ces tests instancient un sous-type de test sans CDI/Quarkus.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@DisplayName("BaseRepository — méthodes non enhancées par Quarkus")
|
||||
class BaseRepositoryUnitTest {
|
||||
|
||||
/** Sous-classe concrète de test — non gérée par CDI. */
|
||||
@SuppressWarnings("unchecked")
|
||||
static class TestRepo extends BaseRepository<Organisation> {
|
||||
private final PanacheQuery<Organisation> mockQuery;
|
||||
|
||||
TestRepo(EntityManager em, PanacheQuery<Organisation> mockQuery) {
|
||||
super(Organisation.class);
|
||||
this.entityManager = em;
|
||||
this.mockQuery = mockQuery;
|
||||
}
|
||||
|
||||
TestRepo(EntityManager em) {
|
||||
this(em, null);
|
||||
}
|
||||
|
||||
/** Surcharge findAll() pour éviter d'appeler le runtime Panache/Quarkus. */
|
||||
@Override
|
||||
public PanacheQuery<Organisation> findAll() {
|
||||
return mockQuery;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findByIdOptional: retourne Optional.of(entity) quand trouvée")
|
||||
void findByIdOptional_found_returnsPresent() {
|
||||
EntityManager em = mock(EntityManager.class);
|
||||
UUID id = UUID.randomUUID();
|
||||
Organisation org = new Organisation();
|
||||
when(em.find(Organisation.class, id)).thenReturn(org);
|
||||
|
||||
TestRepo repo = new TestRepo(em);
|
||||
Optional<Organisation> result = repo.findByIdOptional(id);
|
||||
assertThat(result).isPresent().contains(org);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findByIdOptional: retourne Optional.empty() quand non trouvée")
|
||||
void findByIdOptional_notFound_returnsEmpty() {
|
||||
EntityManager em = mock(EntityManager.class);
|
||||
UUID id = UUID.randomUUID();
|
||||
when(em.find(Organisation.class, id)).thenReturn(null);
|
||||
|
||||
TestRepo repo = new TestRepo(em);
|
||||
Optional<Organisation> result = repo.findByIdOptional(id);
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("listAll: délègue à findAll().list()")
|
||||
void listAll_delegatesToFindAll() {
|
||||
EntityManager em = mock(EntityManager.class);
|
||||
PanacheQuery<Organisation> mockQuery = mock(PanacheQuery.class);
|
||||
Organisation org = new Organisation();
|
||||
when(mockQuery.list()).thenReturn(List.of(org));
|
||||
|
||||
TestRepo repo = new TestRepo(em, mockQuery);
|
||||
List<Organisation> result = repo.listAll();
|
||||
assertThat(result).containsExactly(org);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("count: délègue à findAll().count()")
|
||||
void count_delegatesToFindAll() {
|
||||
EntityManager em = mock(EntityManager.class);
|
||||
PanacheQuery<Organisation> mockQuery = mock(PanacheQuery.class);
|
||||
when(mockQuery.count()).thenReturn(42L);
|
||||
|
||||
TestRepo repo = new TestRepo(em, mockQuery);
|
||||
assertThat(repo.count()).isEqualTo(42L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getEntityManager: retourne l'EntityManager injecté")
|
||||
void getEntityManager_returnsInjectedEntityManager() {
|
||||
EntityManager em = mock(EntityManager.class);
|
||||
TestRepo repo = new TestRepo(em);
|
||||
assertThat(repo.getEntityManager()).isSameAs(em);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("deleteById: retourne false quand entité non trouvée")
|
||||
void deleteById_notFound_returnsFalse() {
|
||||
EntityManager em = mock(EntityManager.class);
|
||||
UUID id = UUID.randomUUID();
|
||||
when(em.find(Organisation.class, id)).thenReturn(null);
|
||||
|
||||
TestRepo repo = new TestRepo(em);
|
||||
assertThat(repo.deleteById(id)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("deleteById: retourne true et supprime quand entité trouvée")
|
||||
void deleteById_found_returnsTrueAndDeletes() {
|
||||
EntityManager em = mock(EntityManager.class);
|
||||
UUID id = UUID.randomUUID();
|
||||
Organisation org = new Organisation();
|
||||
when(em.find(Organisation.class, id)).thenReturn(org);
|
||||
when(em.contains(org)).thenReturn(true);
|
||||
|
||||
TestRepo repo = new TestRepo(em);
|
||||
boolean result = repo.deleteById(id);
|
||||
assertThat(result).isTrue();
|
||||
verify(em).remove(org);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Budget;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@QuarkusTest
|
||||
class BudgetRepositoryTest {
|
||||
|
||||
@Inject
|
||||
BudgetRepository budgetRepository;
|
||||
|
||||
@Inject
|
||||
OrganisationRepository organisationRepository;
|
||||
|
||||
private Organisation newOrganisation() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("Org Budget " + UUID.randomUUID());
|
||||
o.setTypeOrganisation("ASSOCIATION");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("budget-org-" + UUID.randomUUID() + "@test.com");
|
||||
o.setActif(true);
|
||||
organisationRepository.persist(o);
|
||||
return o;
|
||||
}
|
||||
|
||||
private Budget newBudget(Organisation org, String status, int year) {
|
||||
return Budget.builder()
|
||||
.name("Budget test " + UUID.randomUUID())
|
||||
.organisation(org)
|
||||
.period("ANNUAL")
|
||||
.year(year)
|
||||
.status(status)
|
||||
.totalPlanned(BigDecimal.valueOf(100000))
|
||||
.totalRealized(BigDecimal.ZERO)
|
||||
.currency("XOF")
|
||||
.createdById(UUID.randomUUID())
|
||||
.createdAtBudget(LocalDateTime.now())
|
||||
.startDate(LocalDate.of(year, 1, 1))
|
||||
.endDate(LocalDate.of(year, 12, 31))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationWithFilters sans status ni year retourne tous les budgets de l'org")
|
||||
void findByOrganisationWithFilters_noStatusNoYear_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Budget b = newBudget(org, "ACTIVE", 2026);
|
||||
budgetRepository.persist(b);
|
||||
|
||||
List<Budget> list = budgetRepository.findByOrganisationWithFilters(org.getId(), null, null);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationWithFilters avec status seulement couvre la branche status != null et year == null")
|
||||
void findByOrganisationWithFilters_statusOnly_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Budget b = newBudget(org, "ACTIVE", 2026);
|
||||
budgetRepository.persist(b);
|
||||
|
||||
List<Budget> list = budgetRepository.findByOrganisationWithFilters(org.getId(), "ACTIVE", null);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationWithFilters avec year seulement couvre la branche status == null et year != null")
|
||||
void findByOrganisationWithFilters_yearOnly_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Budget b = newBudget(org, "DRAFT", 2026);
|
||||
budgetRepository.persist(b);
|
||||
|
||||
List<Budget> list = budgetRepository.findByOrganisationWithFilters(org.getId(), null, 2026);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationWithFilters avec status et year couvre les deux branches true")
|
||||
void findByOrganisationWithFilters_statusAndYear_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Budget b = newBudget(org, "ACTIVE", 2026);
|
||||
budgetRepository.persist(b);
|
||||
|
||||
List<Budget> list = budgetRepository.findByOrganisationWithFilters(org.getId(), "ACTIVE", 2026);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
// ── Branch coverage manquantes ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* L55 + L67 branch manquante : status != null mais isEmpty() → false
|
||||
* → `if (status != null && !status.isEmpty())` → false (status est "")
|
||||
*/
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationWithFilters avec status vide (empty) couvre la branche isEmpty")
|
||||
void findByOrganisationWithFilters_emptyStatus_treatedAsNoFilter() {
|
||||
Organisation org = newOrganisation();
|
||||
Budget b = newBudget(org, "ACTIVE", 2026);
|
||||
budgetRepository.persist(b);
|
||||
|
||||
// status = "" → !status.isEmpty() est false → branche false → pas filtré par status
|
||||
List<Budget> list = budgetRepository.findByOrganisationWithFilters(org.getId(), "", null);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisation retourne les budgets de l'organisation")
|
||||
void findByOrganisation_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Budget b = newBudget(org, "ACTIVE", 2026);
|
||||
budgetRepository.persist(b);
|
||||
|
||||
List<Budget> list = budgetRepository.findByOrganisation(org.getId());
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findActiveByOrganisation retourne les budgets actifs")
|
||||
void findActiveByOrganisation_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Budget b = newBudget(org, "ACTIVE", 2026);
|
||||
budgetRepository.persist(b);
|
||||
|
||||
List<Budget> list = budgetRepository.findActiveByOrganisation(org.getId());
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countActiveByOrganisation retourne un nombre >= 0")
|
||||
void countActiveByOrganisation_returnsNonNegative() {
|
||||
long count = budgetRepository.countActiveByOrganisation(UUID.randomUUID());
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationAndYear retourne les budgets de l'organisation pour l'année donnée")
|
||||
void findByOrganisationAndYear_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Budget b = newBudget(org, "ACTIVE", 2026);
|
||||
budgetRepository.persist(b);
|
||||
|
||||
List<Budget> list = budgetRepository.findByOrganisationAndYear(org.getId(), 2026);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationAndYear avec année sans budget → retourne liste vide")
|
||||
void findByOrganisationAndYear_wrongYear_returnsEmpty() {
|
||||
Organisation org = newOrganisation();
|
||||
Budget b = newBudget(org, "ACTIVE", 2025);
|
||||
budgetRepository.persist(b);
|
||||
|
||||
List<Budget> list = budgetRepository.findByOrganisationAndYear(org.getId(), 9999);
|
||||
assertThat(list).isNotNull();
|
||||
assertThat(list).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -81,4 +81,12 @@ class CompteComptableRepositoryTest {
|
||||
List<CompteComptable> list = compteComptableRepository.findComptesTresorerie();
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByClasse retourne une liste pour classe 1")
|
||||
void findByClasse_returnsList() {
|
||||
List<CompteComptable> list = compteComptableRepository.findByClasse(1);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +72,28 @@ class CompteWaveRepositoryTest {
|
||||
List<CompteWave> list = compteWaveRepository.findComptesVerifies();
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreId retourne une liste vide pour UUID inexistant")
|
||||
void findByMembreId_inexistant_returnsEmpty() {
|
||||
List<CompteWave> list = compteWaveRepository.findByMembreId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findPrincipalByMembreId retourne empty pour UUID inexistant")
|
||||
void findPrincipalByMembreId_inexistant_returnsEmpty() {
|
||||
java.util.Optional<CompteWave> opt = compteWaveRepository.findPrincipalByMembreId(UUID.randomUUID());
|
||||
assertThat(opt).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findPrincipalByOrganisationId retourne empty pour UUID inexistant")
|
||||
void findPrincipalByOrganisationId_inexistant_returnsEmpty() {
|
||||
java.util.Optional<CompteWave> opt = compteWaveRepository.findPrincipalByOrganisationId(UUID.randomUUID());
|
||||
assertThat(opt).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.communication.ConversationType;
|
||||
import dev.lions.unionflow.server.entity.Conversation;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests d'intégration pour {@link ConversationRepository}.
|
||||
* Couvre les 3 méthodes : findByParticipant, findByIdAndParticipant, findByOrganisation.
|
||||
*/
|
||||
@QuarkusTest
|
||||
class ConversationRepositoryTest {
|
||||
|
||||
@Inject
|
||||
ConversationRepository conversationRepository;
|
||||
|
||||
@Inject
|
||||
OrganisationRepository organisationRepository;
|
||||
|
||||
private Organisation createOrganisation() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("Org Conversation");
|
||||
o.setTypeOrganisation("ASSOCIATION");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("conv-" + UUID.randomUUID() + "@test.com");
|
||||
o.setActif(true);
|
||||
o.setDateCreation(LocalDateTime.now());
|
||||
organisationRepository.persist(o);
|
||||
return o;
|
||||
}
|
||||
|
||||
private Conversation createConversation(String name, Organisation org) {
|
||||
Conversation c = new Conversation();
|
||||
c.setName(name);
|
||||
c.setType(ConversationType.GROUP);
|
||||
c.setOrganisation(org);
|
||||
c.setActif(true);
|
||||
c.setDateCreation(LocalDateTime.now());
|
||||
conversationRepository.persist(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// findByParticipant — branches avec includeArchived=true et false
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByParticipant avec includeArchived=true retourne liste vide si aucune conversation")
|
||||
void findByParticipant_noConversations_returnsEmptyList() {
|
||||
UUID randomMembre = UUID.randomUUID();
|
||||
|
||||
List<Conversation> result = conversationRepository.findByParticipant(randomMembre, true);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByParticipant avec includeArchived=false retourne liste vide si aucune conversation")
|
||||
void findByParticipant_excludeArchived_returnsEmptyList() {
|
||||
UUID randomMembre = UUID.randomUUID();
|
||||
|
||||
List<Conversation> result = conversationRepository.findByParticipant(randomMembre, false);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// findByIdAndParticipant
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByIdAndParticipant retourne empty pour ID et membreId inconnus")
|
||||
void findByIdAndParticipant_unknownIds_returnsEmpty() {
|
||||
Optional<Conversation> result = conversationRepository.findByIdAndParticipant(
|
||||
UUID.randomUUID(), UUID.randomUUID());
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByIdAndParticipant retourne empty pour conversationId inexistant")
|
||||
void findByIdAndParticipant_nonExistentConversation_returnsEmpty() {
|
||||
UUID nonExistentId = UUID.randomUUID();
|
||||
UUID membreId = UUID.randomUUID();
|
||||
|
||||
Optional<Conversation> result = conversationRepository.findByIdAndParticipant(
|
||||
nonExistentId, membreId);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// findByOrganisation
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisation retourne liste vide si organisation sans conversation")
|
||||
void findByOrganisation_noConversations_returnsEmptyList() {
|
||||
Organisation org = createOrganisation();
|
||||
|
||||
List<Conversation> result = conversationRepository.findByOrganisation(org.getId());
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisation retourne les conversations de l'organisation persistées")
|
||||
void findByOrganisation_withConversations_returnsList() {
|
||||
Organisation org = createOrganisation();
|
||||
createConversation("Conv1-" + UUID.randomUUID(), org);
|
||||
createConversation("Conv2-" + UUID.randomUUID(), org);
|
||||
|
||||
List<Conversation> result = conversationRepository.findByOrganisation(org.getId());
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisation retourne liste vide pour organisationId inexistant")
|
||||
void findByOrganisation_unknownOrganisationId_returnsEmptyList() {
|
||||
List<Conversation> result = conversationRepository.findByOrganisation(UUID.randomUUID());
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import dev.lions.unionflow.server.entity.Cotisation;
|
||||
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 io.quarkus.test.TestTransaction;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -138,6 +139,68 @@ class CotisationRepositoryTest {
|
||||
assertThat(stats).containsKeys("totalCotisations", "montantTotal", "montantPaye", "cotisationsPayees", "tauxPaiement");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy avec plusieurs colonnes couvre la branche i>0 et direction ASC")
|
||||
void buildOrderBy_multipleColumns_ascAndDesc() {
|
||||
Page page = new Page(0, 10);
|
||||
// Sort with two columns: first DESC, second ASC — exercises i>0 and Ascending branch
|
||||
Sort sort = Sort.by("annee", Sort.Direction.Descending)
|
||||
.and("mois", Sort.Direction.Ascending);
|
||||
List<Cotisation> list = cotisationRepository.findByMembreId(UUID.randomUUID(), page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy avec une colonne ASC couvre la branche direction Ascending")
|
||||
void buildOrderBy_singleColumnAscending() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("annee", Sort.Direction.Ascending);
|
||||
List<Cotisation> list = cotisationRepository.findByMembreId(UUID.randomUUID(), page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
// ── Branch coverage manquante : buildOrderBy ───────────────────────────
|
||||
|
||||
/**
|
||||
* L586 branch manquante : sort != null mais sort.getColumns().isEmpty() → true
|
||||
* → `if (sort == null || sort.getColumns().isEmpty())` → true via deuxième terme
|
||||
* On crée un Sort non-null avec colonnes vides via réflexion.
|
||||
*/
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy: sort non-null avec colonnes vides → fallback dateEcheance DESC (branche isEmpty)")
|
||||
void buildOrderBy_sortNonNull_emptyColumns_fallback() throws Exception {
|
||||
// Créer un Sort non-null avec liste de colonnes vide via le constructeur privé
|
||||
java.lang.reflect.Constructor<Sort> ctor = Sort.class.getDeclaredConstructor();
|
||||
ctor.setAccessible(true);
|
||||
Sort emptySort = ctor.newInstance();
|
||||
// emptySort.getColumns() retourne une ArrayList vide → isEmpty() = true
|
||||
|
||||
Page page = new Page(0, 10);
|
||||
// Ne doit pas lancer d'exception et retourner une liste (avec ORDER BY c.dateEcheance DESC)
|
||||
List<Cotisation> list = cotisationRepository.findByMembreId(UUID.randomUUID(), page, emptySort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countPayeesByMembreId avec membreId null retourne 0 (branche null true)")
|
||||
void countPayeesByMembreId_nullMembreId_returnsZero() {
|
||||
long result = cotisationRepository.countPayeesByMembreId(null);
|
||||
assertThat(result).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByPeriode avec mois non-null couvre la branche mois != null")
|
||||
void findByPeriode_avecMois_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Cotisation> list = cotisationRepository.findByPeriode(LocalDate.now().getYear(), LocalDate.now().getMonthValue(), page);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("persist puis findByNumeroReference retrouve la cotisation")
|
||||
@@ -163,4 +226,13 @@ class CotisationRepositoryTest {
|
||||
assertThat(found).isPresent();
|
||||
assertThat(found.get().getNumeroReference()).isEqualTo(ref);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByType(String, Page) retourne une liste non-null")
|
||||
void findByType_avecPage_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Cotisation> list = cotisationRepository.findByType("ANNUELLE", page);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,850 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Cotisation;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests unitaires purs pour CotisationRepository.
|
||||
*
|
||||
* <p>Ces tests couvrent les branches ternaires {@code result != null ? result : fallback}
|
||||
* dans les méthodes d'agrégation — branches qui ne peuvent être atteintes en test d'intégration
|
||||
* car COUNT/SUM via JPQL ne retournent jamais null via getSingleResult() avec H2.
|
||||
*
|
||||
* <p>On instancie CotisationRepository directement (sans CDI) et on injecte un EntityManager
|
||||
* mocké par réflexion.
|
||||
*/
|
||||
@DisplayName("CotisationRepository — tests unitaires (branches ternaires null)")
|
||||
class CotisationRepositoryUnitTest {
|
||||
|
||||
/**
|
||||
* Sous-classe concrète non gérée par CDI — permet d'instancier CotisationRepository
|
||||
* sans le contexte Quarkus/CDI.
|
||||
*/
|
||||
static class TestCotisationRepository extends CotisationRepository {
|
||||
TestCotisationRepository(EntityManager em) {
|
||||
super();
|
||||
this.entityManager = em;
|
||||
}
|
||||
}
|
||||
|
||||
private EntityManager em;
|
||||
private TestCotisationRepository repo;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
em = mock(EntityManager.class);
|
||||
repo = new TestCotisationRepository(em);
|
||||
}
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TypedQuery<Long> mockLongQuery(Long returnValue) {
|
||||
TypedQuery<Long> q = mock(TypedQuery.class);
|
||||
when(q.setParameter(anyString(), any())).thenReturn(q);
|
||||
when(q.getSingleResult()).thenReturn(returnValue);
|
||||
return q;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TypedQuery<BigDecimal> mockBigDecimalQuery(BigDecimal returnValue) {
|
||||
TypedQuery<BigDecimal> q = mock(TypedQuery.class);
|
||||
when(q.setParameter(anyString(), any())).thenReturn(q);
|
||||
when(q.getSingleResult()).thenReturn(returnValue);
|
||||
return q;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TypedQuery<Cotisation> mockCotisationQuery(List<Cotisation> results) {
|
||||
TypedQuery<Cotisation> q = mock(TypedQuery.class);
|
||||
when(q.setParameter(anyString(), any())).thenReturn(q);
|
||||
when(q.setFirstResult(anyInt())).thenReturn(q);
|
||||
when(q.setMaxResults(anyInt())).thenReturn(q);
|
||||
when(q.getResultList()).thenReturn(results);
|
||||
return q;
|
||||
}
|
||||
|
||||
// ─── countByMembreId ──────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("countByMembreId: result null → retourne 0L (branche ternaire null)")
|
||||
void countByMembreId_resultNull_returnsZero() {
|
||||
TypedQuery<Long> q = mockLongQuery(null);
|
||||
when(em.createQuery(contains("COUNT(c) FROM Cotisation c WHERE c.membre.id"), eq(Long.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
long result = repo.countByMembreId(UUID.randomUUID());
|
||||
assertThat(result).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("countByMembreId: result non-null → retourne la valeur (branche ternaire non-null)")
|
||||
void countByMembreId_resultNonNull_returnsValue() {
|
||||
TypedQuery<Long> q = mockLongQuery(5L);
|
||||
when(em.createQuery(contains("COUNT(c) FROM Cotisation c WHERE c.membre.id"), eq(Long.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
long result = repo.countByMembreId(UUID.randomUUID());
|
||||
assertThat(result).isEqualTo(5L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("countByMembreId: membreId null → retourne 0L immédiatement (guard clause)")
|
||||
void countByMembreId_nullId_returnsZeroImmediately() {
|
||||
long result = repo.countByMembreId(null);
|
||||
assertThat(result).isEqualTo(0L);
|
||||
verifyNoInteractions(em);
|
||||
}
|
||||
|
||||
// ─── countPayeesByMembreId ─────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("countPayeesByMembreId: result null → retourne 0L")
|
||||
void countPayeesByMembreId_resultNull_returnsZero() {
|
||||
TypedQuery<Long> q = mockLongQuery(null);
|
||||
when(em.createQuery(contains("COUNT(c) FROM Cotisation c WHERE c.membre.id"), eq(Long.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
long result = repo.countPayeesByMembreId(UUID.randomUUID());
|
||||
assertThat(result).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("countPayeesByMembreId: result non-null → retourne la valeur (branche ternaire non-null)")
|
||||
void countPayeesByMembreId_resultNonNull_returnsValue() {
|
||||
TypedQuery<Long> q = mockLongQuery(3L);
|
||||
when(em.createQuery(contains("COUNT(c) FROM Cotisation c WHERE c.membre.id"), eq(Long.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
long result = repo.countPayeesByMembreId(UUID.randomUUID());
|
||||
assertThat(result).isEqualTo(3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("countPayeesByMembreId: membreId null → retourne 0L")
|
||||
void countPayeesByMembreId_nullId_returnsZero() {
|
||||
long result = repo.countPayeesByMembreId(null);
|
||||
assertThat(result).isEqualTo(0L);
|
||||
verifyNoInteractions(em);
|
||||
}
|
||||
|
||||
// ─── countByOrganisationId ─────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("countByOrganisationId: result null → retourne 0L")
|
||||
void countByOrganisationId_resultNull_returnsZero() {
|
||||
TypedQuery<Long> q = mockLongQuery(null);
|
||||
when(em.createQuery(contains("COUNT(c) FROM Cotisation c WHERE c.organisation.id"), eq(Long.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
long result = repo.countByOrganisationId(UUID.randomUUID());
|
||||
assertThat(result).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("countByOrganisationId: organisationId null → retourne 0L")
|
||||
void countByOrganisationId_nullId_returnsZero() {
|
||||
long result = repo.countByOrganisationId(null);
|
||||
assertThat(result).isEqualTo(0L);
|
||||
verifyNoInteractions(em);
|
||||
}
|
||||
|
||||
// ─── countByOrganisationIdAndDatePaiementBetween ───────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("countByOrganisationIdAndDatePaiementBetween: result null → retourne 0L")
|
||||
void countByOrgIdAndDateBetween_resultNull_returnsZero() {
|
||||
TypedQuery<Long> q = mockLongQuery(null);
|
||||
when(em.createQuery(contains("COUNT(c) FROM Cotisation c WHERE c.organisation.id"), eq(Long.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
long result = repo.countByOrganisationIdAndDatePaiementBetween(
|
||||
UUID.randomUUID(), LocalDateTime.now().minusDays(1), LocalDateTime.now());
|
||||
assertThat(result).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("countByOrganisationIdAndDatePaiementBetween: organisationId null → retourne 0L")
|
||||
void countByOrgIdAndDateBetween_nullId_returnsZero() {
|
||||
long result = repo.countByOrganisationIdAndDatePaiementBetween(
|
||||
null, LocalDateTime.now().minusDays(1), LocalDateTime.now());
|
||||
assertThat(result).isEqualTo(0L);
|
||||
verifyNoInteractions(em);
|
||||
}
|
||||
|
||||
// ─── calculerTotalCotisationsPayeesCeMois ─────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalCotisationsPayeesCeMois: total null → retourne ZERO")
|
||||
void calculerTotalPayeesCeMois_totalNull_returnsZero() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("SUM(c.montantPaye) FROM Cotisation c WHERE c.membre.id"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalCotisationsPayeesCeMois(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalCotisationsPayeesCeMois: total non-null → retourne la valeur")
|
||||
void calculerTotalPayeesCeMois_totalNonNull_returnsValue() {
|
||||
BigDecimal expected = BigDecimal.valueOf(5000);
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(expected);
|
||||
when(em.createQuery(contains("SUM(c.montantPaye) FROM Cotisation c WHERE c.membre.id"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalCotisationsPayeesCeMois(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(expected);
|
||||
}
|
||||
|
||||
// ─── countRetardByMembreId ─────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("countRetardByMembreId: count null → retourne 0L")
|
||||
void countRetardByMembreId_countNull_returnsZero() {
|
||||
TypedQuery<Long> q = mockLongQuery(null);
|
||||
when(em.createQuery(contains("COUNT(c) FROM Cotisation c WHERE c.membre.id"), eq(Long.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
long result = repo.countRetardByMembreId(UUID.randomUUID());
|
||||
assertThat(result).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("countRetardByMembreId: count non-null → retourne la valeur")
|
||||
void countRetardByMembreId_countNonNull_returnsValue() {
|
||||
TypedQuery<Long> q = mockLongQuery(3L);
|
||||
when(em.createQuery(contains("COUNT(c) FROM Cotisation c WHERE c.membre.id"), eq(Long.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
long result = repo.countRetardByMembreId(UUID.randomUUID());
|
||||
assertThat(result).isEqualTo(3L);
|
||||
}
|
||||
|
||||
// ─── calculerTotalCotisationsPayeesAnneeEnCours ───────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalCotisationsPayeesAnneeEnCours: total null → retourne ZERO")
|
||||
void calculerTotalPayeesAnnee_totalNull_returnsZero() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("SUM(c.montantPaye) FROM Cotisation c WHERE c.membre.id"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalCotisationsPayeesAnneeEnCours(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalCotisationsPayeesAnneeEnCours: total non-null → retourne la valeur")
|
||||
void calculerTotalPayeesAnnee_totalNonNull_returnsValue() {
|
||||
BigDecimal expected = BigDecimal.valueOf(12000);
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(expected);
|
||||
when(em.createQuery(contains("SUM(c.montantPaye) FROM Cotisation c WHERE c.membre.id"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalCotisationsPayeesAnneeEnCours(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(expected);
|
||||
}
|
||||
|
||||
// ─── calculerTotalCotisationsPayeesToutTemps ──────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalCotisationsPayeesToutTemps: total null → retourne ZERO")
|
||||
void calculerTotalPayeesToutTemps_totalNull_returnsZero() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("SUM(c.montantPaye) FROM Cotisation c WHERE c.membre.id"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalCotisationsPayeesToutTemps(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalCotisationsPayeesToutTemps: total non-null → retourne la valeur")
|
||||
void calculerTotalPayeesToutTemps_totalNonNull_returnsValue() {
|
||||
BigDecimal expected = BigDecimal.valueOf(25000);
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(expected);
|
||||
when(em.createQuery(contains("SUM(c.montantPaye) FROM Cotisation c WHERE c.membre.id"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalCotisationsPayeesToutTemps(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(expected);
|
||||
}
|
||||
|
||||
// ─── calculerTotalMontantDu ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalMontantDu: result null → retourne ZERO")
|
||||
void calculerTotalMontantDu_resultNull_returnsZero() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("COALESCE(SUM(c.montantDu)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalMontantDu(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// ─── calculerTotalMontantPaye ─────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalMontantPaye: result null → retourne ZERO")
|
||||
void calculerTotalMontantPaye_resultNull_returnsZero() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("COALESCE(SUM(c.montantPaye)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalMontantPaye(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// ─── sommeMontantPayeParStatut ─────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("sommeMontantPayeParStatut: result null → retourne ZERO")
|
||||
void sommeMontantPayeParStatut_resultNull_returnsZero() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("COALESCE(SUM(c.montantPaye)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.sommeMontantPayeParStatut("PAYEE");
|
||||
assertThat(result).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("sommeMontantPayeParStatut: result non-null → retourne la valeur")
|
||||
void sommeMontantPayeParStatut_resultNonNull_returnsValue() {
|
||||
BigDecimal expected = BigDecimal.valueOf(8000);
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(expected);
|
||||
when(em.createQuery(contains("COALESCE(SUM(c.montantPaye)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.sommeMontantPayeParStatut("PAYEE");
|
||||
assertThat(result).isEqualByComparingTo(expected);
|
||||
}
|
||||
|
||||
// ─── getStatistiquesPeriode ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("getStatistiquesPeriode avec mois: totalCotisations null → tauxPaiement=0.0")
|
||||
void getStatistiquesPeriode_withMois_nullTotal_tauxZero() {
|
||||
int annee = LocalDate.now().getYear();
|
||||
int mois = LocalDate.now().getMonthValue();
|
||||
|
||||
// Un seul mock Long pour toutes les requêtes COUNT (countQuery + payeesQuery)
|
||||
TypedQuery<Long> nullLongQ = mockLongQuery(null);
|
||||
// Un seul mock BigDecimal pour toutes les requêtes SUM
|
||||
TypedQuery<BigDecimal> nullBdQ = mockBigDecimalQuery(null);
|
||||
|
||||
when(em.createQuery(anyString(), eq(Long.class))).thenReturn(nullLongQ);
|
||||
when(em.createQuery(anyString(), eq(BigDecimal.class))).thenReturn(nullBdQ);
|
||||
|
||||
Map<String, Object> stats = repo.getStatistiquesPeriode(annee, mois);
|
||||
assertThat(stats).containsKey("tauxPaiement");
|
||||
assertThat((Double) stats.get("tauxPaiement")).isEqualTo(0.0);
|
||||
assertThat(stats.get("totalCotisations")).isEqualTo(0L);
|
||||
assertThat(stats.get("montantTotal")).isEqualTo(BigDecimal.ZERO);
|
||||
assertThat(stats.get("montantPaye")).isEqualTo(BigDecimal.ZERO);
|
||||
assertThat(stats.get("cotisationsPayees")).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getStatistiquesPeriode sans mois: totalCotisations null → tauxPaiement=0.0")
|
||||
void getStatistiquesPeriode_withoutMois_nullTotal_tauxZero() {
|
||||
int annee = LocalDate.now().getYear();
|
||||
|
||||
TypedQuery<Long> nullLongQ = mockLongQuery(null);
|
||||
TypedQuery<BigDecimal> nullBdQ = mockBigDecimalQuery(null);
|
||||
|
||||
when(em.createQuery(anyString(), eq(Long.class))).thenReturn(nullLongQ);
|
||||
when(em.createQuery(anyString(), eq(BigDecimal.class))).thenReturn(nullBdQ);
|
||||
|
||||
Map<String, Object> stats = repo.getStatistiquesPeriode(annee, null);
|
||||
assertThat(stats).containsKey("tauxPaiement");
|
||||
assertThat((Double) stats.get("tauxPaiement")).isEqualTo(0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getStatistiquesPeriode: totalCotisations > 0 → taux calculé (branche totalCotisations > 0)")
|
||||
void getStatistiquesPeriode_nonZeroTotal_tauxCalcule() {
|
||||
int annee = LocalDate.now().getYear();
|
||||
|
||||
// totalCotisations = 10, cotisationsPayees = 8 → taux = 80.0
|
||||
TypedQuery<Long> countQ = mockLongQuery(10L);
|
||||
TypedQuery<Long> payeesQ = mockLongQuery(8L);
|
||||
TypedQuery<BigDecimal> montantTotalQ = mockBigDecimalQuery(BigDecimal.valueOf(100000));
|
||||
TypedQuery<BigDecimal> montantPayeQ = mockBigDecimalQuery(BigDecimal.valueOf(80000));
|
||||
|
||||
// getStatistiquesPeriode(annee, null) branche else : crée countQuery en premier,
|
||||
// puis montantTotalQuery, montantPayeQuery, payeesQuery — dans cet ordre pour Long.
|
||||
// On utilise thenReturn avec cascade : 1er appel → countQ(10), 2ème → payeesQ(8)
|
||||
when(em.createQuery(anyString(), eq(Long.class)))
|
||||
.thenReturn(countQ)
|
||||
.thenReturn(payeesQ);
|
||||
when(em.createQuery(anyString(), eq(BigDecimal.class)))
|
||||
.thenReturn(montantTotalQ)
|
||||
.thenReturn(montantPayeQ);
|
||||
|
||||
Map<String, Object> stats = repo.getStatistiquesPeriode(annee, null);
|
||||
assertThat(stats).containsKey("tauxPaiement");
|
||||
double taux = (double) stats.get("tauxPaiement");
|
||||
assertThat(taux).isEqualTo(80.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getStatistiquesPeriode avec mois: totalCotisations > 0 → taux calculé (branche totalCotisations > 0)")
|
||||
void getStatistiquesPeriode_withMois_nonZeroTotal_tauxCalcule() {
|
||||
int annee = LocalDate.now().getYear();
|
||||
int mois = LocalDate.now().getMonthValue();
|
||||
|
||||
TypedQuery<Long> countQ = mockLongQuery(5L);
|
||||
TypedQuery<Long> payeesQ = mockLongQuery(4L);
|
||||
TypedQuery<BigDecimal> montantTotalQ = mockBigDecimalQuery(BigDecimal.valueOf(50000));
|
||||
TypedQuery<BigDecimal> montantPayeQ = mockBigDecimalQuery(BigDecimal.valueOf(40000));
|
||||
|
||||
when(em.createQuery(anyString(), eq(Long.class)))
|
||||
.thenReturn(countQ)
|
||||
.thenReturn(payeesQ);
|
||||
when(em.createQuery(anyString(), eq(BigDecimal.class)))
|
||||
.thenReturn(montantTotalQ)
|
||||
.thenReturn(montantPayeQ);
|
||||
|
||||
Map<String, Object> stats = repo.getStatistiquesPeriode(annee, mois);
|
||||
assertThat(stats).containsKey("tauxPaiement");
|
||||
double taux = (double) stats.get("tauxPaiement");
|
||||
assertThat(taux).isEqualTo(80.0);
|
||||
}
|
||||
|
||||
// ─── sumMontantsPayes ─────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantsPayes: result null → retourne ZERO")
|
||||
void sumMontantsPayes_resultNull_returnsZero() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("COALESCE(SUM(c.montantPaye)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.sumMontantsPayes(
|
||||
UUID.randomUUID(), LocalDateTime.now().minusDays(30), LocalDateTime.now());
|
||||
assertThat(result).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantsPayes: result non-null → retourne la valeur")
|
||||
void sumMontantsPayes_resultNonNull_returnsValue() {
|
||||
BigDecimal expected = BigDecimal.valueOf(15000);
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(expected);
|
||||
when(em.createQuery(contains("COALESCE(SUM(c.montantPaye)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.sumMontantsPayes(
|
||||
UUID.randomUUID(), LocalDateTime.now().minusDays(30), LocalDateTime.now());
|
||||
assertThat(result).isEqualByComparingTo(expected);
|
||||
}
|
||||
|
||||
// ─── sumMontantsEnAttente ─────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantsEnAttente: result null → retourne ZERO")
|
||||
void sumMontantsEnAttente_resultNull_returnsZero() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("COALESCE(SUM(c.montantDu)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.sumMontantsEnAttente(
|
||||
UUID.randomUUID(), LocalDateTime.now().minusDays(30), LocalDateTime.now());
|
||||
assertThat(result).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantsEnAttente: result non-null → retourne la valeur")
|
||||
void sumMontantsEnAttente_resultNonNull_returnsValue() {
|
||||
BigDecimal expected = BigDecimal.valueOf(20000);
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(expected);
|
||||
when(em.createQuery(contains("COALESCE(SUM(c.montantDu)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.sumMontantsEnAttente(
|
||||
UUID.randomUUID(), LocalDateTime.now().minusDays(30), LocalDateTime.now());
|
||||
assertThat(result).isEqualByComparingTo(expected);
|
||||
}
|
||||
|
||||
// ─── buildOrderBy (via appels publics) ───────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("buildOrderBy: sort null → fallback 'c.dateEcheance DESC' (via findByMembreId)")
|
||||
void buildOrderBy_sortNull_returnsFallback() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.findByMembreId(UUID.randomUUID(), page, null);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildOrderBy: sort non-null avec colonnes → construit ORDER BY (via findByMembreId)")
|
||||
void buildOrderBy_sortWithColumns_buildsOrderBy() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
Sort sort = Sort.descending("dateEcheance");
|
||||
List<Cotisation> result = repo.findByMembreId(UUID.randomUUID(), page, sort);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildOrderBy: sort non-null avec colonnes multiples → couvre la branche i>0")
|
||||
void buildOrderBy_multipleColumns_coversIPlusBranch() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
Sort sort = Sort.by("dateEcheance", Sort.Direction.Descending).and("statut", Sort.Direction.Ascending);
|
||||
List<Cotisation> result = repo.findByMembreId(UUID.randomUUID(), page, sort);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildOrderBy: sort non-null avec colonnes vides → fallback 'c.dateEcheance DESC' (branche isEmpty=true)")
|
||||
void buildOrderBy_emptySort_returnsFallback() throws Exception {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
// Créer un Sort non-null avec liste de colonnes vide via réflexion
|
||||
Sort emptySort;
|
||||
try {
|
||||
java.lang.reflect.Constructor<Sort> ctor = Sort.class.getDeclaredConstructor();
|
||||
ctor.setAccessible(true);
|
||||
emptySort = ctor.newInstance();
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Sort n'a pas de constructeur no-arg : test skip
|
||||
return;
|
||||
}
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.findByMembreId(UUID.randomUUID(), page, emptySort);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
// ─── findByOrganisationIdIn ───────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("findByOrganisationIdIn: organisationIds null → retourne liste vide (branche null)")
|
||||
void findByOrganisationIdIn_nullIds_returnsEmptyList() {
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.findByOrganisationIdIn(null, page, null);
|
||||
assertThat(result).isEmpty();
|
||||
verifyNoInteractions(em);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findByOrganisationIdIn: organisationIds vide → retourne liste vide (branche isEmpty)")
|
||||
void findByOrganisationIdIn_emptyIds_returnsEmptyList() {
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.findByOrganisationIdIn(Set.of(), page, null);
|
||||
assertThat(result).isEmpty();
|
||||
verifyNoInteractions(em);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findByOrganisationIdIn: sort null → utilise ORDER BY dateEcheance DESC (branche sort null)")
|
||||
void findByOrganisationIdIn_withIds_sortNull_returnsList() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
java.util.Set<UUID> ids = Set.of(UUID.randomUUID());
|
||||
List<Cotisation> result = repo.findByOrganisationIdIn(ids, page, null);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findByOrganisationIdIn: sort non-null → utilise buildOrderBy (branche sort non-null)")
|
||||
void findByOrganisationIdIn_withIds_sortNonNull_returnsList() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
java.util.Set<UUID> ids = Set.of(UUID.randomUUID());
|
||||
Sort sort = Sort.descending("dateEcheance");
|
||||
List<Cotisation> result = repo.findByOrganisationIdIn(ids, page, sort);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
// ─── findEnAttenteByOrganisationIdIn ─────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("findEnAttenteByOrganisationIdIn: null → retourne liste vide")
|
||||
void findEnAttenteByOrganisationIdIn_nullIds_returnsEmptyList() {
|
||||
List<Cotisation> result = repo.findEnAttenteByOrganisationIdIn(null);
|
||||
assertThat(result).isEmpty();
|
||||
verifyNoInteractions(em);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findEnAttenteByOrganisationIdIn: vide → retourne liste vide")
|
||||
void findEnAttenteByOrganisationIdIn_emptyIds_returnsEmptyList() {
|
||||
List<Cotisation> result = repo.findEnAttenteByOrganisationIdIn(Set.of());
|
||||
assertThat(result).isEmpty();
|
||||
verifyNoInteractions(em);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findEnAttenteByOrganisationIdIn: ids valides → exécute la requête")
|
||||
void findEnAttenteByOrganisationIdIn_withIds_executesQuery() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
java.util.Set<UUID> ids = Set.of(UUID.randomUUID());
|
||||
List<Cotisation> result = repo.findEnAttenteByOrganisationIdIn(ids);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
// ─── searchAdvanced ───────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("searchAdvanced: tous paramètres null → WHERE 1=1, ORDER BY dateEcheance DESC")
|
||||
void searchAdvanced_allNull_returnsAll() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.searchAdvanced(null, null, null, null, page, null);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("searchAdvanced: membreId non-null → ajoute filtre membre")
|
||||
void searchAdvanced_withMembreId_addsFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.searchAdvanced(UUID.randomUUID(), null, null, null, page, null);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("searchAdvanced: statut non-null non-blank → ajoute filtre statut")
|
||||
void searchAdvanced_withStatut_addsFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.searchAdvanced(null, "PAYEE", null, null, page, null);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("searchAdvanced: statut blank → pas de filtre statut (branche isBlank=true)")
|
||||
void searchAdvanced_withBlankStatut_noFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.searchAdvanced(null, " ", null, null, page, null);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("searchAdvanced: typeCotisation non-null non-blank → ajoute filtre type")
|
||||
void searchAdvanced_withTypeCotisation_addsFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.searchAdvanced(null, null, "ANNUELLE", null, page, null);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("searchAdvanced: typeCotisation blank → pas de filtre type (branche isBlank=true)")
|
||||
void searchAdvanced_withBlankType_noFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.searchAdvanced(null, null, "", null, page, null);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("searchAdvanced: annee non-null → ajoute filtre annee")
|
||||
void searchAdvanced_withAnnee_addsFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.searchAdvanced(null, null, null, 2024, page, null);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("searchAdvanced: sort non-null → utilise buildOrderBy (branche sort != null)")
|
||||
void searchAdvanced_withSort_usesOrderBy() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
Sort sort = Sort.descending("dateEcheance");
|
||||
List<Cotisation> result = repo.searchAdvanced(null, null, null, null, page, sort);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
// ─── calculerTotalCotisationsAnneeEnCours ─────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalCotisationsAnneeEnCours: total null → retourne ZERO")
|
||||
void calculerTotalCotisationsAnneeEnCours_null_returnsZero() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("SUM(c.montantDu) FROM Cotisation c WHERE c.membre.id"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalCotisationsAnneeEnCours(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalCotisationsAnneeEnCours: total non-null → retourne la valeur")
|
||||
void calculerTotalCotisationsAnneeEnCours_nonNull_returnsValue() {
|
||||
BigDecimal expected = BigDecimal.valueOf(30000);
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(expected);
|
||||
when(em.createQuery(contains("SUM(c.montantDu) FROM Cotisation c WHERE c.membre.id"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalCotisationsAnneeEnCours(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(expected);
|
||||
}
|
||||
|
||||
// ─── calculerTotalMontantPaye ─────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("calculerTotalMontantPaye: result non-null → retourne la valeur (branche non-null)")
|
||||
void calculerTotalMontantPaye_nonNull_returnsValue() {
|
||||
BigDecimal expected = BigDecimal.valueOf(7500);
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(expected);
|
||||
when(em.createQuery(contains("COALESCE(SUM(c.montantPaye)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
BigDecimal result = repo.calculerTotalMontantPaye(UUID.randomUUID());
|
||||
assertThat(result).isEqualByComparingTo(expected);
|
||||
}
|
||||
|
||||
// ─── incrementerNombreRappels ─────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("incrementerNombreRappels: cotisation non trouvée → retourne false")
|
||||
void incrementerNombreRappels_cotisationNotFound_returnsFalse() {
|
||||
when(em.find(eq(Cotisation.class), any())).thenReturn(null);
|
||||
|
||||
boolean result = repo.incrementerNombreRappels(UUID.randomUUID());
|
||||
assertThat(result).isFalse();
|
||||
}
|
||||
|
||||
// ─── rechercheAvancee branches manquantes ────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("rechercheAvancee: statut non-null non-empty → ajoute filtre statut (branche isEmpty=false)")
|
||||
void rechercheAvancee_withStatut_addsFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.rechercheAvancee(null, "PAYEE", null, null, null, page);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("rechercheAvancee: typeCotisation non-null non-empty → ajoute filtre type (branche isEmpty=false)")
|
||||
void rechercheAvancee_withTypeCotisation_addsFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.rechercheAvancee(null, null, "MENSUELLE", null, null, page);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("rechercheAvancee: mois non-null → ajoute filtre mois (branche mois != null)")
|
||||
void rechercheAvancee_withMois_addsFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.rechercheAvancee(null, null, null, null, 6, page);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("rechercheAvancee: statut empty → pas de filtre statut (branche isEmpty=true)")
|
||||
void rechercheAvancee_withEmptyStatut_noFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.rechercheAvancee(null, "", null, null, null, page);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("rechercheAvancee: typeCotisation empty → pas de filtre type (branche isEmpty=true)")
|
||||
void rechercheAvancee_withEmptyType_noFilter() {
|
||||
TypedQuery<Cotisation> q = mockCotisationQuery(List.of());
|
||||
when(em.createQuery(anyString(), eq(Cotisation.class))).thenReturn(q);
|
||||
|
||||
io.quarkus.panache.common.Page page = new io.quarkus.panache.common.Page(0, 10);
|
||||
List<Cotisation> result = repo.rechercheAvancee(null, null, "", null, null, page);
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
// ─── buildOrderBy (via réflexion) ─────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("buildOrderBy: sort null → retourne 'c.dateEcheance DESC' (branche sort==null)")
|
||||
void buildOrderBy_sortNull_retourneDefault() throws Exception {
|
||||
java.lang.reflect.Method m = CotisationRepository.class.getDeclaredMethod("buildOrderBy", Sort.class);
|
||||
m.setAccessible(true);
|
||||
String result = (String) m.invoke(repo, (Sort) null);
|
||||
assertThat(result).isEqualTo("c.dateEcheance DESC");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildOrderBy: sort non-null, columns vides → retourne 'c.dateEcheance DESC' (branche getColumns().isEmpty()=true)")
|
||||
void buildOrderBy_sortColumnsVides_retourneDefault() throws Exception {
|
||||
java.lang.reflect.Method m = CotisationRepository.class.getDeclaredMethod("buildOrderBy", Sort.class);
|
||||
m.setAccessible(true);
|
||||
Sort sort = org.mockito.Mockito.mock(Sort.class);
|
||||
when(sort.getColumns()).thenReturn(java.util.Collections.emptyList());
|
||||
String result = (String) m.invoke(repo, sort);
|
||||
assertThat(result).isEqualTo("c.dateEcheance DESC");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("buildOrderBy: sort avec colonne ASC → 'c.nomColonne ASC' (branche direction != Descending)")
|
||||
void buildOrderBy_sortAvecColonneAsc_retourneAsc() throws Exception {
|
||||
java.lang.reflect.Method m = CotisationRepository.class.getDeclaredMethod("buildOrderBy", Sort.class);
|
||||
m.setAccessible(true);
|
||||
Sort sort = Sort.ascending("montantDu");
|
||||
String result = (String) m.invoke(repo, sort);
|
||||
assertThat(result).contains("ASC");
|
||||
}
|
||||
}
|
||||
@@ -153,4 +153,231 @@ class DemandeAideRepositoryTest {
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getTitre()).isEqualTo("Test aide");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("sumMontantDemandeByOrganisationId retourne present quand montant > 0")
|
||||
void sumMontantDemandeByOrganisationId_withData_returnsPresent() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
DemandeAide d = DemandeAide.builder()
|
||||
.titre("Aide montant test")
|
||||
.description("Test")
|
||||
.typeAide(TypeAide.AIDE_COTISATION)
|
||||
.statut(StatutAide.EN_ATTENTE)
|
||||
.montantDemande(BigDecimal.valueOf(500))
|
||||
.dateDemande(LocalDateTime.now())
|
||||
.demandeur(membre)
|
||||
.organisation(org)
|
||||
.urgence(false)
|
||||
.build();
|
||||
demandeAideRepository.persist(d);
|
||||
Optional<BigDecimal> sum = demandeAideRepository.sumMontantDemandeByOrganisationId(org.getId());
|
||||
assertThat(sum).isPresent();
|
||||
assertThat(sum.get()).isGreaterThan(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("sumMontantApprouveByOrganisationId retourne empty quand aucun montant approuvé")
|
||||
void sumMontantApprouveByOrganisationId_empty_returnsEmpty() {
|
||||
Optional<BigDecimal> sum = demandeAideRepository.sumMontantApprouveByOrganisationId(UUID.randomUUID());
|
||||
assertThat(sum).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("sumMontantApprouveByOrganisationId retourne present quand montant approuvé > 0")
|
||||
void sumMontantApprouveByOrganisationId_withData_returnsPresent() {
|
||||
Organisation org = newOrganisation();
|
||||
Membre membre = newMembre();
|
||||
DemandeAide d = DemandeAide.builder()
|
||||
.titre("Aide approuvée test")
|
||||
.description("Test")
|
||||
.typeAide(TypeAide.AIDE_COTISATION)
|
||||
.statut(StatutAide.APPROUVEE)
|
||||
.montantDemande(BigDecimal.valueOf(800))
|
||||
.montantApprouve(BigDecimal.valueOf(700))
|
||||
.dateDemande(LocalDateTime.now())
|
||||
.demandeur(membre)
|
||||
.organisation(org)
|
||||
.urgence(false)
|
||||
.build();
|
||||
demandeAideRepository.persist(d);
|
||||
Optional<BigDecimal> sum = demandeAideRepository.sumMontantApprouveByOrganisationId(org.getId());
|
||||
assertThat(sum).isPresent();
|
||||
assertThat(sum.get()).isGreaterThan(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// ── Branches manquantes ───────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationId(Page, Sort): sort null → ORDER BY dateDemande DESC (défaut)")
|
||||
void findByOrganisationId_withPage_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationId(Page, Sort): sort non-null → buildOrderBy utilisé")
|
||||
void findByOrganisationId_withPage_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDemande", Sort.Direction.Descending);
|
||||
List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy: colonnes multiples → branche i>0 (via findByOrganisationId)")
|
||||
void buildOrderBy_multipleColumns_coversIPlusBranch() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDemande", Sort.Direction.Descending).and("titre", Sort.Direction.Ascending);
|
||||
List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy: direction ASC → branche Ascending (via findByOrganisationId)")
|
||||
void buildOrderBy_ascendingDirection_coversAscBranch() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("titre", Sort.Direction.Ascending);
|
||||
List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy: sort non-null colonnes vides → fallback dateDemande DESC (branche isEmpty L255)")
|
||||
void buildOrderBy_sortNonNullEmptyColumns_fallback() throws Exception {
|
||||
// Sort non-null avec liste colonnes vide → L255: isEmpty() = true → fallback "d.dateDemande DESC"
|
||||
java.lang.reflect.Constructor<Sort> ctor = Sort.class.getDeclaredConstructor();
|
||||
ctor.setAccessible(true);
|
||||
Sort emptySort = ctor.newInstance();
|
||||
|
||||
Page page = new Page(0, 10);
|
||||
List<DemandeAide> list = demandeAideRepository.findByOrganisationId(UUID.randomUUID(), page, emptySort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
// ── Méthodes non couvertes ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByDemandeurId retourne une liste")
|
||||
void findByDemandeurId_returnsList() {
|
||||
List<DemandeAide> list = demandeAideRepository.findByDemandeurId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByStatutAndOrganisationId retourne une liste")
|
||||
void findByStatutAndOrganisationId_returnsList() {
|
||||
List<DemandeAide> list = demandeAideRepository.findByStatutAndOrganisationId(
|
||||
StatutAide.EN_ATTENTE, UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findUrgentesByOrganisationId retourne une liste")
|
||||
void findUrgentesByOrganisationId_returnsList() {
|
||||
List<DemandeAide> list = demandeAideRepository.findUrgentesByOrganisationId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByPeriodeAndOrganisationId retourne une liste")
|
||||
void findByPeriodeAndOrganisationId_returnsList() {
|
||||
LocalDateTime debut = LocalDateTime.now().minusDays(30);
|
||||
LocalDateTime fin = LocalDateTime.now();
|
||||
List<DemandeAide> list = demandeAideRepository.findByPeriodeAndOrganisationId(
|
||||
debut, fin, UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countByStatut retourne un nombre >= 0")
|
||||
void countByStatut_returnsNonNegative() {
|
||||
long count = demandeAideRepository.countByStatut("EN_ATTENTE");
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countByStatutAndOrganisationId retourne un nombre >= 0")
|
||||
void countByStatutAndOrganisationId_returnsNonNegative() {
|
||||
long count = demandeAideRepository.countByStatutAndOrganisationId("EN_ATTENTE", UUID.randomUUID());
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findRecentesByOrganisationId retourne une liste")
|
||||
void findRecentesByOrganisationId_returnsList() {
|
||||
List<DemandeAide> list = demandeAideRepository.findRecentesByOrganisationId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEnAttenteDepuis retourne une liste")
|
||||
void findEnAttenteDepuis_returnsList() {
|
||||
List<DemandeAide> list = demandeAideRepository.findEnAttenteDepuis(30);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByEvaluateurId retourne une liste")
|
||||
void findByEvaluateurId_returnsList() {
|
||||
List<DemandeAide> list = demandeAideRepository.findByEvaluateurId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEnCoursEvaluationByEvaluateurId retourne une liste")
|
||||
void findEnCoursEvaluationByEvaluateurId_returnsList() {
|
||||
List<DemandeAide> list = demandeAideRepository.findEnCoursEvaluationByEvaluateurId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countDemandesApprouvees retourne un nombre >= 0")
|
||||
void countDemandesApprouvees_returnsNonNegative() {
|
||||
LocalDateTime debut = LocalDateTime.now().minusDays(30);
|
||||
LocalDateTime fin = LocalDateTime.now();
|
||||
long count = demandeAideRepository.countDemandesApprouvees(UUID.randomUUID(), debut, fin);
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countDemandes retourne un nombre >= 0")
|
||||
void countDemandes_returnsNonNegative() {
|
||||
LocalDateTime debut = LocalDateTime.now().minusDays(30);
|
||||
LocalDateTime fin = LocalDateTime.now();
|
||||
long count = demandeAideRepository.countDemandes(UUID.randomUUID(), debut, fin);
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("sumMontantsAccordes retourne un montant >= 0")
|
||||
void sumMontantsAccordes_returnsNonNegative() {
|
||||
LocalDateTime debut = LocalDateTime.now().minusDays(30);
|
||||
LocalDateTime fin = LocalDateTime.now();
|
||||
java.math.BigDecimal total = demandeAideRepository.sumMontantsAccordes(UUID.randomUUID(), debut, fin);
|
||||
assertThat(total).isNotNull();
|
||||
assertThat(total).isGreaterThanOrEqualTo(java.math.BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Tests unitaires purs pour DemandeAideRepository.
|
||||
* Couvre les cas limites de sumMontantDemandeByOrganisationId et sumMontantApprouveByOrganisationId.
|
||||
*/
|
||||
@DisplayName("DemandeAideRepository — tests unitaires (branches ternaires null)")
|
||||
class DemandeAideRepositoryUnitTest {
|
||||
|
||||
/**
|
||||
* Sous-classe concrète non gérée par CDI pour instancier DemandeAideRepository sans Quarkus.
|
||||
*/
|
||||
static class TestDemandeAideRepository extends DemandeAideRepository {
|
||||
TestDemandeAideRepository(EntityManager em) {
|
||||
super();
|
||||
this.entityManager = em;
|
||||
}
|
||||
}
|
||||
|
||||
private EntityManager em;
|
||||
private TestDemandeAideRepository repo;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
em = mock(EntityManager.class);
|
||||
repo = new TestDemandeAideRepository(em);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private TypedQuery<BigDecimal> mockBigDecimalQuery(BigDecimal returnValue) {
|
||||
TypedQuery<BigDecimal> q = mock(TypedQuery.class);
|
||||
when(q.setParameter(anyString(), any())).thenReturn(q);
|
||||
when(q.getSingleResult()).thenReturn(returnValue);
|
||||
return q;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantDemandeByOrganisationId: result null → retourne Optional.empty()")
|
||||
void sumMontantDemandeByOrganisationId_resultNull_returnsEmpty() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("COALESCE(SUM(d.montantDemande)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
Optional<BigDecimal> result = repo.sumMontantDemandeByOrganisationId(UUID.randomUUID());
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantDemandeByOrganisationId: result > 0 → retourne Optional.present()")
|
||||
void sumMontantDemandeByOrganisationId_resultPositive_returnsPresent() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(BigDecimal.valueOf(5000));
|
||||
when(em.createQuery(contains("COALESCE(SUM(d.montantDemande)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
Optional<BigDecimal> result = repo.sumMontantDemandeByOrganisationId(UUID.randomUUID());
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.get()).isEqualByComparingTo(BigDecimal.valueOf(5000));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantDemandeByOrganisationId: result = 0 → retourne Optional.empty()")
|
||||
void sumMontantDemandeByOrganisationId_resultZero_returnsEmpty() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(BigDecimal.ZERO);
|
||||
when(em.createQuery(contains("COALESCE(SUM(d.montantDemande)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
Optional<BigDecimal> result = repo.sumMontantDemandeByOrganisationId(UUID.randomUUID());
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantApprouveByOrganisationId: result null → retourne Optional.empty()")
|
||||
void sumMontantApprouveByOrganisationId_resultNull_returnsEmpty() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(null);
|
||||
when(em.createQuery(contains("COALESCE(SUM(d.montantApprouve)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
Optional<BigDecimal> result = repo.sumMontantApprouveByOrganisationId(UUID.randomUUID());
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantApprouveByOrganisationId: result > 0 → retourne Optional.present()")
|
||||
void sumMontantApprouveByOrganisationId_resultPositive_returnsPresent() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(BigDecimal.valueOf(3000));
|
||||
when(em.createQuery(contains("COALESCE(SUM(d.montantApprouve)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
Optional<BigDecimal> result = repo.sumMontantApprouveByOrganisationId(UUID.randomUUID());
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.get()).isEqualByComparingTo(BigDecimal.valueOf(3000));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("sumMontantApprouveByOrganisationId: result = 0 → retourne Optional.empty()")
|
||||
void sumMontantApprouveByOrganisationId_resultZero_returnsEmpty() {
|
||||
TypedQuery<BigDecimal> q = mockBigDecimalQuery(BigDecimal.ZERO);
|
||||
when(em.createQuery(contains("COALESCE(SUM(d.montantApprouve)"), eq(BigDecimal.class)))
|
||||
.thenReturn(q);
|
||||
|
||||
Optional<BigDecimal> result = repo.sumMontantApprouveByOrganisationId(UUID.randomUUID());
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -73,4 +73,12 @@ class DocumentRepositoryTest {
|
||||
List<Document> list = documentRepository.findAllActifs();
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByHashSha256 retourne empty pour hash inexistant")
|
||||
void findByHashSha256_inexistant_returnsEmpty() {
|
||||
java.util.Optional<Document> opt = documentRepository.findByHashSha256("sha256-" + UUID.randomUUID());
|
||||
assertThat(opt).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,4 +83,28 @@ class EcritureComptableRepositoryTest {
|
||||
List<EcritureComptable> list = ecritureComptableRepository.findNonPointees();
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByPaiementId retourne une liste (vide si paiement inexistant)")
|
||||
void findByPaiementId_inexistant_returnsEmpty() {
|
||||
List<EcritureComptable> list = ecritureComptableRepository.findByPaiementId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByLettrage retourne une liste (vide si lettrage inexistant)")
|
||||
void findByLettrage_inexistant_returnsEmpty() {
|
||||
List<EcritureComptable> list = ecritureComptableRepository.findByLettrage("LET-INEX-" + UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationId retourne une liste")
|
||||
void findByOrganisationId_returnsList() {
|
||||
List<EcritureComptable> list = ecritureComptableRepository.findByOrganisationId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,59 @@ class EvenementRepositoryTest {
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("rechercheAvancee avec sort non null couvre la branche sort != null")
|
||||
void rechercheAvancee_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDebut", Sort.Direction.Descending);
|
||||
List<Evenement> list = evenementRepository.rechercheAvancee(
|
||||
null, null, null, null, null,
|
||||
null, null, null, null, null,
|
||||
page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("rechercheAvancee avec tous les critères fournis couvre toutes les branches de filtre")
|
||||
void rechercheAvancee_allFilters_returnsList() {
|
||||
Organisation org = newOrganisation();
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.rechercheAvancee(
|
||||
"test", // recherche non null
|
||||
"PLANIFIE", // statut non null
|
||||
"REUNION", // type non null
|
||||
org.getId(), // organisationId non null
|
||||
UUID.randomUUID(), // organisateurId non null
|
||||
LocalDateTime.now().minusDays(1), // dateDebutMin non null
|
||||
LocalDateTime.now().plusDays(30), // dateDebutMax non null
|
||||
Boolean.TRUE, // visiblePublic non null
|
||||
Boolean.FALSE, // inscriptionRequise non null
|
||||
Boolean.TRUE, // actif non null
|
||||
page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
// ── Branch coverage manquante ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* L306 branch manquante : recherche != null mais recherche.trim().isEmpty() → false
|
||||
* → `if (recherche != null && !recherche.trim().isEmpty())` → false (recherche = " ")
|
||||
*/
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("rechercheAvancee avec recherche blank (whitespace) couvre la branche trim().isEmpty()")
|
||||
void rechercheAvancee_blankRecherche_treatedAsNoFilter() {
|
||||
Page page = new Page(0, 10);
|
||||
// recherche = " " → trim().isEmpty() est true → !trim().isEmpty() est false → ignoré
|
||||
List<Evenement> list = evenementRepository.rechercheAvancee(
|
||||
" ", null, null, null, null,
|
||||
null, null, null, null, null,
|
||||
page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("persist puis findById et findByTitre retrouvent l'événement")
|
||||
@@ -139,4 +192,212 @@ class EvenementRepositoryTest {
|
||||
Optional<Evenement> byTitre = evenementRepository.findByTitre("Événement test");
|
||||
assertThat(byTitre).isPresent();
|
||||
}
|
||||
|
||||
// ── Branches manquantes ───────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findAllActifs(Page, Sort): sort non-null → ORDER BY (branche sort != null)")
|
||||
void findAllActifs_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDebut", Sort.Direction.Ascending);
|
||||
List<Evenement> list = evenementRepository.findAllActifs(page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findAllActifs(Page, Sort): sort null → pas de ORDER BY")
|
||||
void findAllActifs_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.findAllActifs(page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByStatut(Page, Sort): sort non-null → ORDER BY")
|
||||
void findByStatut_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDebut", Sort.Direction.Descending);
|
||||
List<Evenement> list = evenementRepository.findByStatut("PLANIFIE", page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByStatut(Page, Sort): sort null → pas de ORDER BY")
|
||||
void findByStatut_withPage_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.findByStatut("PLANIFIE", page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByType(Page, Sort): sort non-null → ORDER BY")
|
||||
void findByType_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("titre", Sort.Direction.Ascending);
|
||||
List<Evenement> list = evenementRepository.findByType("REUNION", page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByType(Page, Sort): sort null → pas de ORDER BY")
|
||||
void findByType_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.findByType("REUNION", page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisation(Page, Sort): sort non-null → ORDER BY")
|
||||
void findByOrganisation_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDebut", Sort.Direction.Descending);
|
||||
List<Evenement> list = evenementRepository.findByOrganisation(UUID.randomUUID(), page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisation(Page, Sort): sort null → pas de ORDER BY")
|
||||
void findByOrganisation_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.findByOrganisation(UUID.randomUUID(), page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEvenementsAVenir(Page, Sort): sort non-null → ORDER BY")
|
||||
void findEvenementsAVenir_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDebut", Sort.Direction.Ascending);
|
||||
List<Evenement> list = evenementRepository.findEvenementsAVenir(page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEvenementsAVenir(Page, Sort): sort null → pas de ORDER BY")
|
||||
void findEvenementsAVenir_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.findEvenementsAVenir(page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countByOrganisationId: null → retourne 0L")
|
||||
void countByOrganisationId_null_returnsZero() {
|
||||
long count = evenementRepository.countByOrganisationId(null);
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countByOrganisationId: org valide → exécute la requête")
|
||||
void countByOrganisationId_valid_executesQuery() {
|
||||
long count = evenementRepository.countByOrganisationId(UUID.randomUUID());
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countActifsByOrganisationId: null → retourne 0L")
|
||||
void countActifsByOrganisationId_null_returnsZero() {
|
||||
long count = evenementRepository.countActifsByOrganisationId(null);
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countActifsByOrganisationId: org valide → exécute la requête")
|
||||
void countActifsByOrganisationId_valid_executesQuery() {
|
||||
long count = evenementRepository.countActifsByOrganisationId(UUID.randomUUID());
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEvenementsAVenirByOrganisationId: null → retourne liste vide")
|
||||
void findEvenementsAVenirByOrganisationId_null_returnsEmpty() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.findEvenementsAVenirByOrganisationId(null, page, null);
|
||||
assertThat(list).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEvenementsAVenirByOrganisationId: org valide + sort non-null → ORDER BY")
|
||||
void findEvenementsAVenirByOrganisationId_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDebut", Sort.Direction.Ascending);
|
||||
List<Evenement> list = evenementRepository.findEvenementsAVenirByOrganisationId(UUID.randomUUID(), page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEvenementsAVenirByOrganisationId: org valide + sort null → ORDER BY dateDebut ASC (défaut)")
|
||||
void findEvenementsAVenirByOrganisationId_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.findEvenementsAVenirByOrganisationId(UUID.randomUUID(), page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEvenementsPublics(Page, Sort): sort non-null → ORDER BY")
|
||||
void findEvenementsPublics_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDebut", Sort.Direction.Ascending);
|
||||
List<Evenement> list = evenementRepository.findEvenementsPublics(page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEvenementsPublics(Page, Sort): sort null → pas de ORDER BY")
|
||||
void findEvenementsPublics_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.findEvenementsPublics(page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy: colonnes multiples → couvre la branche i>0 (via findAllActifs)")
|
||||
void buildOrderBy_multipleColumns_coversIPlusBranch() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateDebut", Sort.Direction.Descending).and("titre", Sort.Direction.Ascending);
|
||||
List<Evenement> list = evenementRepository.findAllActifs(page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy: sort non-null colonnes vides → fallback dateDebut (branche isEmpty L478)")
|
||||
void buildOrderBy_sortNonNullEmptyColumns_fallback() throws Exception {
|
||||
// Sort non-null avec liste colonnes vide → L478: isEmpty() = true → fallback "e.dateDebut"
|
||||
java.lang.reflect.Constructor<Sort> ctor = Sort.class.getDeclaredConstructor();
|
||||
ctor.setAccessible(true);
|
||||
Sort emptySort = ctor.newInstance();
|
||||
|
||||
Page page = new Page(0, 10);
|
||||
List<Evenement> list = evenementRepository.findAllActifs(page, emptySort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByType(String) retourne une liste non-null")
|
||||
void findByType_simpleString_returnsList() {
|
||||
List<Evenement> list = evenementRepository.findByType("CONFERENCE");
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.FeedbackEvenement;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@QuarkusTest
|
||||
@DisplayName("FeedbackEvenementRepository — tests des méthodes de requête")
|
||||
class FeedbackEvenementRepositoryTest {
|
||||
|
||||
@Inject
|
||||
FeedbackEvenementRepository feedbackRepository;
|
||||
|
||||
@Inject
|
||||
EntityManager em;
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
private Membre persistMembre() {
|
||||
Membre m = new Membre();
|
||||
m.setNumeroMembre("FB-MEM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
|
||||
m.setPrenom("Fatoumata");
|
||||
m.setNom("Feedback");
|
||||
m.setEmail("fb." + UUID.randomUUID() + "@test.com");
|
||||
m.setDateNaissance(LocalDate.of(1992, 3, 10));
|
||||
m.setActif(true);
|
||||
em.persist(m);
|
||||
em.flush();
|
||||
return m;
|
||||
}
|
||||
|
||||
private Evenement persistEvenement() {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Evénement Test " + UUID.randomUUID().toString().substring(0, 8));
|
||||
e.setDateDebut(LocalDateTime.now().plusDays(1));
|
||||
e.setStatut("PLANIFIE");
|
||||
e.setInscriptionRequise(false);
|
||||
e.setVisiblePublic(true);
|
||||
e.setActif(true);
|
||||
em.persist(e);
|
||||
em.flush();
|
||||
return e;
|
||||
}
|
||||
|
||||
private FeedbackEvenement persistFeedback(Membre membre, Evenement evenement, int note, String statut) {
|
||||
FeedbackEvenement fb = FeedbackEvenement.builder()
|
||||
.membre(membre)
|
||||
.evenement(evenement)
|
||||
.note(note)
|
||||
.commentaire("Commentaire test")
|
||||
.dateFeedback(LocalDateTime.now())
|
||||
.moderationStatut(statut)
|
||||
.build();
|
||||
fb.setActif(true);
|
||||
em.persist(fb);
|
||||
em.flush();
|
||||
return fb;
|
||||
}
|
||||
|
||||
// ─── findByMembreAndEvenement ─────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreAndEvenement retourne le feedback pour le couple membre/événement")
|
||||
void findByMembreAndEvenement_existant_retourneFeedback() {
|
||||
Membre membre = persistMembre();
|
||||
Evenement evenement = persistEvenement();
|
||||
FeedbackEvenement fb = persistFeedback(membre, evenement, 4, "PUBLIE");
|
||||
|
||||
Optional<FeedbackEvenement> result = feedbackRepository.findByMembreAndEvenement(
|
||||
membre.getId(), evenement.getId());
|
||||
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.get().getId()).isEqualTo(fb.getId());
|
||||
assertThat(result.get().getNote()).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreAndEvenement retourne empty pour combinaison inexistante")
|
||||
void findByMembreAndEvenement_inexistant_retourneEmpty() {
|
||||
Optional<FeedbackEvenement> result = feedbackRepository.findByMembreAndEvenement(
|
||||
UUID.randomUUID(), UUID.randomUUID());
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
// ─── findPubliesByEvenement ───────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findPubliesByEvenement retourne uniquement les feedbacks publiés")
|
||||
void findPubliesByEvenement_retourneSeulementPublies() {
|
||||
Membre m1 = persistMembre();
|
||||
Membre m2 = persistMembre();
|
||||
Evenement evenement = persistEvenement();
|
||||
persistFeedback(m1, evenement, 5, "PUBLIE");
|
||||
persistFeedback(m2, evenement, 3, "EN_ATTENTE");
|
||||
|
||||
List<FeedbackEvenement> result = feedbackRepository.findPubliesByEvenement(evenement.getId());
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result.get(0).getModerationStatut()).isEqualTo("PUBLIE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findPubliesByEvenement retourne liste vide pour événement sans feedback publié")
|
||||
void findPubliesByEvenement_aucunPublie_retourneListe() {
|
||||
Evenement evenement = persistEvenement();
|
||||
Membre m = persistMembre();
|
||||
persistFeedback(m, evenement, 2, "EN_ATTENTE");
|
||||
|
||||
List<FeedbackEvenement> result = feedbackRepository.findPubliesByEvenement(evenement.getId());
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findPubliesByEvenement retourne liste vide pour événement sans feedback")
|
||||
void findPubliesByEvenement_aucunFeedback_retourneListeVide() {
|
||||
Evenement evenement = persistEvenement();
|
||||
|
||||
List<FeedbackEvenement> result = feedbackRepository.findPubliesByEvenement(evenement.getId());
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
// ─── findAllByEvenement ───────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findAllByEvenement retourne tous les feedbacks actifs (tous statuts)")
|
||||
void findAllByEvenement_retourneTousLesFeedbacks() {
|
||||
Membre m1 = persistMembre();
|
||||
Membre m2 = persistMembre();
|
||||
Membre m3 = persistMembre();
|
||||
Evenement evenement = persistEvenement();
|
||||
persistFeedback(m1, evenement, 5, "PUBLIE");
|
||||
persistFeedback(m2, evenement, 3, "EN_ATTENTE");
|
||||
persistFeedback(m3, evenement, 2, "REJETE");
|
||||
|
||||
List<FeedbackEvenement> result = feedbackRepository.findAllByEvenement(evenement.getId());
|
||||
|
||||
assertThat(result).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findAllByEvenement retourne liste vide pour événement sans feedback")
|
||||
void findAllByEvenement_aucunFeedback_retourneListeVide() {
|
||||
Evenement evenement = persistEvenement();
|
||||
|
||||
List<FeedbackEvenement> result = feedbackRepository.findAllByEvenement(evenement.getId());
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
// ─── calculateAverageNote ─────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("calculateAverageNote retourne la moyenne des notes publiées")
|
||||
void calculateAverageNote_avecFeedbacksPublies_retourneMoyenne() {
|
||||
Membre m1 = persistMembre();
|
||||
Membre m2 = persistMembre();
|
||||
Evenement evenement = persistEvenement();
|
||||
persistFeedback(m1, evenement, 4, "PUBLIE");
|
||||
persistFeedback(m2, evenement, 2, "PUBLIE");
|
||||
|
||||
Double avg = feedbackRepository.calculateAverageNote(evenement.getId());
|
||||
|
||||
assertThat(avg).isEqualTo(3.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("calculateAverageNote retourne 0.0 pour événement sans feedback publié")
|
||||
void calculateAverageNote_aucunFeedbackPublie_retourneZero() {
|
||||
Evenement evenement = persistEvenement();
|
||||
|
||||
Double avg = feedbackRepository.calculateAverageNote(evenement.getId());
|
||||
|
||||
assertThat(avg).isEqualTo(0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("calculateAverageNote ignore les feedbacks en attente de modération")
|
||||
void calculateAverageNote_ignoreFeedbacksEnAttente() {
|
||||
Membre m1 = persistMembre();
|
||||
Membre m2 = persistMembre();
|
||||
Evenement evenement = persistEvenement();
|
||||
persistFeedback(m1, evenement, 5, "PUBLIE");
|
||||
persistFeedback(m2, evenement, 1, "EN_ATTENTE"); // doit être ignoré
|
||||
|
||||
Double avg = feedbackRepository.calculateAverageNote(evenement.getId());
|
||||
|
||||
assertThat(avg).isEqualTo(5.0); // seul le feedback PUBLIE compte
|
||||
}
|
||||
|
||||
// ─── countPubliesByEvenement ──────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countPubliesByEvenement compte correctement les feedbacks publiés")
|
||||
void countPubliesByEvenement_avecFeedbacks_compteCorrectement() {
|
||||
Membre m1 = persistMembre();
|
||||
Membre m2 = persistMembre();
|
||||
Membre m3 = persistMembre();
|
||||
Evenement evenement = persistEvenement();
|
||||
persistFeedback(m1, evenement, 5, "PUBLIE");
|
||||
persistFeedback(m2, evenement, 4, "PUBLIE");
|
||||
persistFeedback(m3, evenement, 3, "EN_ATTENTE");
|
||||
|
||||
long count = feedbackRepository.countPubliesByEvenement(evenement.getId());
|
||||
|
||||
assertThat(count).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countPubliesByEvenement retourne 0 pour événement sans feedback publié")
|
||||
void countPubliesByEvenement_aucunPublie_retourneZero() {
|
||||
Evenement evenement = persistEvenement();
|
||||
|
||||
long count = feedbackRepository.countPubliesByEvenement(evenement.getId());
|
||||
|
||||
assertThat(count).isEqualTo(0);
|
||||
}
|
||||
|
||||
// ─── findEnAttente ────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEnAttente retourne les feedbacks en attente de modération")
|
||||
void findEnAttente_retourneFeedbacksEnAttente() {
|
||||
Membre m1 = persistMembre();
|
||||
Membre m2 = persistMembre();
|
||||
Evenement evenement = persistEvenement();
|
||||
persistFeedback(m1, evenement, 5, "EN_ATTENTE");
|
||||
persistFeedback(m2, evenement, 3, "PUBLIE"); // ne doit pas apparaître
|
||||
|
||||
List<FeedbackEvenement> result = feedbackRepository.findEnAttente();
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).isNotEmpty();
|
||||
result.forEach(fb -> assertThat(fb.getModerationStatut()).isEqualTo("EN_ATTENTE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEnAttente retourne liste vide si aucun feedback en attente")
|
||||
void findEnAttente_aucunEnAttente_retourneListeVide() {
|
||||
// Dans un test isolé avec DB vide ou feedbacks tous publiés
|
||||
// On vérifie uniquement que la méthode fonctionne sans erreur
|
||||
List<FeedbackEvenement> result = feedbackRepository.findEnAttente();
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
// ─── Tests des méthodes métier sur l'entité FeedbackEvenement ────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("mettreEnAttente change le statut à EN_ATTENTE")
|
||||
void mettreEnAttente_changStatutEnAttente() {
|
||||
Membre m = persistMembre();
|
||||
Evenement e = persistEvenement();
|
||||
FeedbackEvenement fb = persistFeedback(m, e, 4, "PUBLIE");
|
||||
|
||||
fb.mettreEnAttente("Contenu inapproprié");
|
||||
|
||||
assertThat(fb.getModerationStatut()).isEqualTo("EN_ATTENTE");
|
||||
assertThat(fb.getRaisonModeration()).isEqualTo("Contenu inapproprié");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("publier change le statut à PUBLIE et efface la raison")
|
||||
void publier_changStatutPublie() {
|
||||
Membre m = persistMembre();
|
||||
Evenement e = persistEvenement();
|
||||
FeedbackEvenement fb = persistFeedback(m, e, 3, "EN_ATTENTE");
|
||||
fb.setRaisonModeration("ancienne raison");
|
||||
|
||||
fb.publier();
|
||||
|
||||
assertThat(fb.getModerationStatut()).isEqualTo("PUBLIE");
|
||||
assertThat(fb.getRaisonModeration()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("rejeter change le statut à REJETE avec une raison")
|
||||
void rejeter_changStatutRejete() {
|
||||
Membre m = persistMembre();
|
||||
Evenement e = persistEvenement();
|
||||
FeedbackEvenement fb = persistFeedback(m, e, 1, "EN_ATTENTE");
|
||||
|
||||
fb.rejeter("Non conforme aux règles");
|
||||
|
||||
assertThat(fb.getModerationStatut()).isEqualTo("REJETE");
|
||||
assertThat(fb.getRaisonModeration()).isEqualTo("Non conforme aux règles");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isPublie retourne true pour statut PUBLIE")
|
||||
void isPublie_statutPublie_retourneTrue() {
|
||||
Membre m = persistMembre();
|
||||
Evenement e = persistEvenement();
|
||||
FeedbackEvenement fb = persistFeedback(m, e, 5, "PUBLIE");
|
||||
|
||||
assertThat(fb.isPublie()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isPublie retourne false pour statut EN_ATTENTE")
|
||||
void isPublie_statutEnAttente_retourneFalse() {
|
||||
Membre m = persistMembre();
|
||||
Evenement e = persistEvenement();
|
||||
FeedbackEvenement fb = persistFeedback(m, e, 5, "EN_ATTENTE");
|
||||
|
||||
assertThat(fb.isPublie()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.InscriptionEvenement;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests pour {@link InscriptionEvenementRepository} — couvre les 6 méthodes
|
||||
* non testées (findByMembreAndEvenement, findByMembre, findByEvenement,
|
||||
* findConfirmeesByEvenement, countConfirmeesByEvenement, isMembreInscrit, softDelete).
|
||||
*/
|
||||
@QuarkusTest
|
||||
class InscriptionEvenementRepositoryTest {
|
||||
|
||||
@Inject
|
||||
InscriptionEvenementRepository inscriptionRepository;
|
||||
|
||||
@Inject
|
||||
MembreRepository membreRepository;
|
||||
|
||||
@Inject
|
||||
EvenementRepository evenementRepository;
|
||||
|
||||
@Inject
|
||||
OrganisationRepository organisationRepository;
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private Membre persistMembre() {
|
||||
Membre m = new Membre();
|
||||
m.setNumeroMembre("INSC-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
m.setPrenom("Inscrit");
|
||||
m.setNom("Test");
|
||||
m.setEmail("insc-" + UUID.randomUUID() + "@test.com");
|
||||
m.setDateNaissance(LocalDate.of(1990, 1, 1));
|
||||
m.setActif(true);
|
||||
membreRepository.persist(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
private Organisation persistOrganisation() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("Org Insc " + UUID.randomUUID().toString().substring(0, 8));
|
||||
o.setTypeOrganisation("ASSOCIATION");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("org-insc-" + UUID.randomUUID() + "@test.com");
|
||||
o.setActif(true);
|
||||
organisationRepository.persist(o);
|
||||
return o;
|
||||
}
|
||||
|
||||
private Evenement persistEvenement(Organisation org) {
|
||||
Evenement e = new Evenement();
|
||||
e.setTitre("Evenement Test " + UUID.randomUUID().toString().substring(0, 8));
|
||||
e.setDateDebut(LocalDateTime.now().plusDays(7));
|
||||
e.setStatut("PLANIFIE");
|
||||
e.setActif(true);
|
||||
evenementRepository.persist(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
private InscriptionEvenement persistInscription(Membre membre, Evenement evenement, String statut) {
|
||||
InscriptionEvenement ie = new InscriptionEvenement();
|
||||
ie.setMembre(membre);
|
||||
ie.setEvenement(evenement);
|
||||
ie.setStatut(statut);
|
||||
ie.setDateInscription(LocalDateTime.now());
|
||||
ie.setActif(true);
|
||||
inscriptionRepository.persist(ie);
|
||||
return ie;
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreAndEvenement retourne l'inscription existante")
|
||||
void findByMembreAndEvenement_returnsInscription() {
|
||||
Membre m = persistMembre();
|
||||
Organisation o = persistOrganisation();
|
||||
Evenement e = persistEvenement(o);
|
||||
persistInscription(m, e, "CONFIRMEE");
|
||||
|
||||
Optional<InscriptionEvenement> found = inscriptionRepository
|
||||
.findByMembreAndEvenement(m.getId(), e.getId());
|
||||
assertThat(found).isPresent();
|
||||
assertThat(found.get().getMembre().getId()).isEqualTo(m.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreAndEvenement retourne empty si aucune inscription")
|
||||
void findByMembreAndEvenement_noInscription_returnsEmpty() {
|
||||
Optional<InscriptionEvenement> found = inscriptionRepository
|
||||
.findByMembreAndEvenement(UUID.randomUUID(), UUID.randomUUID());
|
||||
assertThat(found).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembre retourne les inscriptions du membre")
|
||||
void findByMembre_returnsList() {
|
||||
Membre m = persistMembre();
|
||||
Organisation o = persistOrganisation();
|
||||
Evenement e1 = persistEvenement(o);
|
||||
Evenement e2 = persistEvenement(o);
|
||||
persistInscription(m, e1, "CONFIRMEE");
|
||||
persistInscription(m, e2, "EN_ATTENTE");
|
||||
|
||||
List<InscriptionEvenement> list = inscriptionRepository.findByMembre(m.getId());
|
||||
assertThat(list).hasSizeGreaterThanOrEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembre retourne liste vide pour membre sans inscription")
|
||||
void findByMembre_noInscriptions_returnsEmpty() {
|
||||
List<InscriptionEvenement> list = inscriptionRepository.findByMembre(UUID.randomUUID());
|
||||
assertThat(list).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByEvenement retourne les inscriptions de l'événement")
|
||||
void findByEvenement_returnsList() {
|
||||
Membre m1 = persistMembre();
|
||||
Membre m2 = persistMembre();
|
||||
Organisation o = persistOrganisation();
|
||||
Evenement e = persistEvenement(o);
|
||||
persistInscription(m1, e, "CONFIRMEE");
|
||||
persistInscription(m2, e, "EN_ATTENTE");
|
||||
|
||||
List<InscriptionEvenement> list = inscriptionRepository.findByEvenement(e.getId());
|
||||
assertThat(list).hasSizeGreaterThanOrEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findConfirmeesByEvenement retourne uniquement les inscriptions CONFIRMEE")
|
||||
void findConfirmeesByEvenement_onlyConfirmees() {
|
||||
Membre m1 = persistMembre();
|
||||
Membre m2 = persistMembre();
|
||||
Organisation o = persistOrganisation();
|
||||
Evenement e = persistEvenement(o);
|
||||
persistInscription(m1, e, "CONFIRMEE");
|
||||
persistInscription(m2, e, "EN_ATTENTE");
|
||||
|
||||
List<InscriptionEvenement> confirmees = inscriptionRepository.findConfirmeesByEvenement(e.getId());
|
||||
assertThat(confirmees).isNotEmpty();
|
||||
assertThat(confirmees).allMatch(ie -> "CONFIRMEE".equals(ie.getStatut()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countConfirmeesByEvenement retourne le bon compte")
|
||||
void countConfirmeesByEvenement_returnsCount() {
|
||||
Membre m1 = persistMembre();
|
||||
Membre m2 = persistMembre();
|
||||
Organisation o = persistOrganisation();
|
||||
Evenement e = persistEvenement(o);
|
||||
persistInscription(m1, e, "CONFIRMEE");
|
||||
persistInscription(m2, e, "EN_ATTENTE");
|
||||
|
||||
long count = inscriptionRepository.countConfirmeesByEvenement(e.getId());
|
||||
assertThat(count).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isMembreInscrit retourne true si inscription CONFIRMEE")
|
||||
void isMembreInscrit_confirme_returnsTrue() {
|
||||
Membre m = persistMembre();
|
||||
Organisation o = persistOrganisation();
|
||||
Evenement e = persistEvenement(o);
|
||||
persistInscription(m, e, "CONFIRMEE");
|
||||
|
||||
boolean inscrit = inscriptionRepository.isMembreInscrit(m.getId(), e.getId());
|
||||
assertThat(inscrit).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isMembreInscrit retourne false si membre non inscrit")
|
||||
void isMembreInscrit_nonInscrit_returnsFalse() {
|
||||
boolean inscrit = inscriptionRepository.isMembreInscrit(UUID.randomUUID(), UUID.randomUUID());
|
||||
assertThat(inscrit).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("softDelete marque l'inscription comme inactive")
|
||||
void softDelete_setsInactif() {
|
||||
Membre m = persistMembre();
|
||||
Organisation o = persistOrganisation();
|
||||
Evenement e = persistEvenement(o);
|
||||
InscriptionEvenement ie = persistInscription(m, e, "CONFIRMEE");
|
||||
|
||||
inscriptionRepository.softDelete(ie);
|
||||
|
||||
assertThat(ie.getActif()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement;
|
||||
import dev.lions.unionflow.server.api.enums.paiement.TypeObjetIntentionPaiement;
|
||||
import dev.lions.unionflow.server.entity.IntentionPaiement;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@QuarkusTest
|
||||
@DisplayName("IntentionPaiementRepository — tests des méthodes de requête")
|
||||
class IntentionPaiementRepositoryTest {
|
||||
|
||||
@Inject
|
||||
IntentionPaiementRepository intentionRepository;
|
||||
|
||||
@Inject
|
||||
EntityManager em;
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
private Membre persistMembre() {
|
||||
Membre m = new Membre();
|
||||
m.setNumeroMembre("IP-MEM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
|
||||
m.setPrenom("Ibrahima");
|
||||
m.setNom("PaiementTest");
|
||||
m.setEmail("ip." + UUID.randomUUID() + "@test.com");
|
||||
m.setDateNaissance(LocalDate.of(1985, 6, 15));
|
||||
m.setActif(true);
|
||||
em.persist(m);
|
||||
em.flush();
|
||||
return m;
|
||||
}
|
||||
|
||||
private IntentionPaiement persistIntention(Membre utilisateur, String sessionId) {
|
||||
IntentionPaiement intention = IntentionPaiement.builder()
|
||||
.utilisateur(utilisateur)
|
||||
.montantTotal(BigDecimal.valueOf(5000))
|
||||
.typeObjet(TypeObjetIntentionPaiement.COTISATION)
|
||||
.statut(StatutIntentionPaiement.INITIEE)
|
||||
.build();
|
||||
intention.setWaveCheckoutSessionId(sessionId);
|
||||
intention.setActif(true);
|
||||
em.persist(intention);
|
||||
em.flush();
|
||||
return intention;
|
||||
}
|
||||
|
||||
// ─── Tests findByWaveCheckoutSessionId ──────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByWaveCheckoutSessionId retourne l'intention avec le sessionId correspondant")
|
||||
void findByWaveCheckoutSessionId_sessionIdExistant_retourneIntention() {
|
||||
Membre membre = persistMembre();
|
||||
String sessionId = "wave-session-" + UUID.randomUUID();
|
||||
persistIntention(membre, sessionId);
|
||||
|
||||
Optional<IntentionPaiement> result = intentionRepository.findByWaveCheckoutSessionId(sessionId);
|
||||
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.get().getWaveCheckoutSessionId()).isEqualTo(sessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByWaveCheckoutSessionId retourne empty pour sessionId inexistant")
|
||||
void findByWaveCheckoutSessionId_sessionIdInexistant_retourneEmpty() {
|
||||
Optional<IntentionPaiement> result = intentionRepository.findByWaveCheckoutSessionId("session-inexistante");
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByWaveCheckoutSessionId retourne empty pour sessionId null")
|
||||
void findByWaveCheckoutSessionId_null_retourneEmpty() {
|
||||
Optional<IntentionPaiement> result = intentionRepository.findByWaveCheckoutSessionId(null);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByWaveCheckoutSessionId retourne empty pour sessionId blank")
|
||||
void findByWaveCheckoutSessionId_blank_retourneEmpty() {
|
||||
Optional<IntentionPaiement> result = intentionRepository.findByWaveCheckoutSessionId(" ");
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByWaveCheckoutSessionId retourne empty pour chaîne vide")
|
||||
void findByWaveCheckoutSessionId_chaineVide_retourneEmpty() {
|
||||
Optional<IntentionPaiement> result = intentionRepository.findByWaveCheckoutSessionId("");
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
// ─── Tests méthodes métier ───────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isActive retourne true pour statut INITIEE")
|
||||
void isActive_statutInitiee_retourneTrue() {
|
||||
Membre membre = persistMembre();
|
||||
IntentionPaiement intention = persistIntention(membre, "s-" + UUID.randomUUID());
|
||||
intention.setStatut(StatutIntentionPaiement.INITIEE);
|
||||
|
||||
assertThat(intention.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isActive retourne true pour statut EN_COURS")
|
||||
void isActive_statutEnCours_retourneTrue() {
|
||||
Membre membre = persistMembre();
|
||||
IntentionPaiement intention = persistIntention(membre, "s-" + UUID.randomUUID());
|
||||
intention.setStatut(StatutIntentionPaiement.EN_COURS);
|
||||
|
||||
assertThat(intention.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isActive retourne false pour statut COMPLETEE")
|
||||
void isActive_statutCompletee_retourneFalse() {
|
||||
Membre membre = persistMembre();
|
||||
IntentionPaiement intention = persistIntention(membre, "s-" + UUID.randomUUID());
|
||||
intention.setStatut(StatutIntentionPaiement.COMPLETEE);
|
||||
|
||||
assertThat(intention.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isExpiree retourne true quand dateExpiration est dans le passé")
|
||||
void isExpiree_dateExpirEePassee_retourneTrue() {
|
||||
Membre membre = persistMembre();
|
||||
IntentionPaiement intention = persistIntention(membre, "s-" + UUID.randomUUID());
|
||||
intention.setDateExpiration(LocalDateTime.now().minusMinutes(5));
|
||||
|
||||
assertThat(intention.isExpiree()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isExpiree retourne false quand dateExpiration est dans le futur")
|
||||
void isExpiree_dateExpirationFutur_retourneFalse() {
|
||||
Membre membre = persistMembre();
|
||||
IntentionPaiement intention = persistIntention(membre, "s-" + UUID.randomUUID());
|
||||
intention.setDateExpiration(LocalDateTime.now().plusHours(1));
|
||||
|
||||
assertThat(intention.isExpiree()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isExpiree retourne false quand dateExpiration est null")
|
||||
void isExpiree_dateExpirationNull_retourneFalse() {
|
||||
Membre membre = persistMembre();
|
||||
IntentionPaiement intention = persistIntention(membre, "s-" + UUID.randomUUID());
|
||||
intention.setDateExpiration(null);
|
||||
|
||||
assertThat(intention.isExpiree()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isCompletee retourne true pour statut COMPLETEE")
|
||||
void isCompletee_statutCompletee_retourneTrue() {
|
||||
Membre membre = persistMembre();
|
||||
IntentionPaiement intention = persistIntention(membre, "s-" + UUID.randomUUID());
|
||||
intention.setStatut(StatutIntentionPaiement.COMPLETEE);
|
||||
|
||||
assertThat(intention.isCompletee()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isCompletee retourne false pour statut INITIEE")
|
||||
void isCompletee_statutInitiee_retourneFalse() {
|
||||
Membre membre = persistMembre();
|
||||
IntentionPaiement intention = persistIntention(membre, "s-" + UUID.randomUUID());
|
||||
intention.setStatut(StatutIntentionPaiement.INITIEE);
|
||||
|
||||
assertThat(intention.isCompletee()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("persist et findById fonctionnent correctement")
|
||||
void persistAndFindById_retourneIntentionPersistee() {
|
||||
Membre membre = persistMembre();
|
||||
String sessionId = "wave-persist-" + UUID.randomUUID();
|
||||
IntentionPaiement intention = persistIntention(membre, sessionId);
|
||||
|
||||
IntentionPaiement found = intentionRepository.findById(intention.getId());
|
||||
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getMontantTotal()).isEqualByComparingTo(BigDecimal.valueOf(5000));
|
||||
assertThat(found.getTypeObjet()).isEqualTo(TypeObjetIntentionPaiement.COTISATION);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.MembreOrganisation;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@QuarkusTest
|
||||
@DisplayName("MembreOrganisationRepository — tests de la méthode findByMembreIdAndOrganisationId")
|
||||
class MembreOrganisationRepositoryTest {
|
||||
|
||||
@Inject
|
||||
MembreOrganisationRepository membreOrgRepository;
|
||||
|
||||
@Inject
|
||||
EntityManager em;
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
private Membre persistMembre() {
|
||||
Membre m = new Membre();
|
||||
m.setNumeroMembre("MO-MEM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
|
||||
m.setPrenom("Koffi");
|
||||
m.setNom("OrgTest");
|
||||
m.setEmail("mo." + UUID.randomUUID() + "@test.com");
|
||||
m.setDateNaissance(LocalDate.of(1988, 4, 20));
|
||||
m.setActif(true);
|
||||
em.persist(m);
|
||||
em.flush();
|
||||
return m;
|
||||
}
|
||||
|
||||
private Organisation persistOrganisation() {
|
||||
Organisation org = new Organisation();
|
||||
org.setNom("Org MO Test " + UUID.randomUUID().toString().substring(0, 8));
|
||||
org.setTypeOrganisation("ASSOCIATION");
|
||||
org.setStatut("ACTIVE");
|
||||
org.setEmail("orgmo." + UUID.randomUUID() + "@test.com");
|
||||
org.setDateCreation(LocalDateTime.now());
|
||||
org.setActif(true);
|
||||
em.persist(org);
|
||||
em.flush();
|
||||
return org;
|
||||
}
|
||||
|
||||
private MembreOrganisation persistLien(Membre membre, Organisation organisation) {
|
||||
MembreOrganisation mo = MembreOrganisation.builder()
|
||||
.membre(membre)
|
||||
.organisation(organisation)
|
||||
.statutMembre(StatutMembre.ACTIF)
|
||||
.dateAdhesion(LocalDate.now())
|
||||
.build();
|
||||
mo.setActif(true);
|
||||
em.persist(mo);
|
||||
em.flush();
|
||||
return mo;
|
||||
}
|
||||
|
||||
// ─── Tests ───────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreIdAndOrganisationId retourne le lien existant")
|
||||
void findByMembreIdAndOrganisationId_lienExistant_retourneLien() {
|
||||
Membre membre = persistMembre();
|
||||
Organisation org = persistOrganisation();
|
||||
MembreOrganisation mo = persistLien(membre, org);
|
||||
|
||||
Optional<MembreOrganisation> result = membreOrgRepository.findByMembreIdAndOrganisationId(
|
||||
membre.getId(), org.getId());
|
||||
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.get().getId()).isEqualTo(mo.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreIdAndOrganisationId retourne empty si le lien n'existe pas")
|
||||
void findByMembreIdAndOrganisationId_lienInexistant_retourneEmpty() {
|
||||
Optional<MembreOrganisation> result = membreOrgRepository.findByMembreIdAndOrganisationId(
|
||||
UUID.randomUUID(), UUID.randomUUID());
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreIdAndOrganisationId ne confond pas les membres")
|
||||
void findByMembreIdAndOrganisationId_autresMembres_retourneEmpty() {
|
||||
Membre membre1 = persistMembre();
|
||||
Membre membre2 = persistMembre();
|
||||
Organisation org = persistOrganisation();
|
||||
persistLien(membre1, org);
|
||||
|
||||
// On cherche avec membre2 qui n'est pas dans cette organisation
|
||||
Optional<MembreOrganisation> result = membreOrgRepository.findByMembreIdAndOrganisationId(
|
||||
membre2.getId(), org.getId());
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreIdAndOrganisationId ne confond pas les organisations")
|
||||
void findByMembreIdAndOrganisationId_autresOrgs_retourneEmpty() {
|
||||
Membre membre = persistMembre();
|
||||
Organisation org1 = persistOrganisation();
|
||||
Organisation org2 = persistOrganisation();
|
||||
persistLien(membre, org1);
|
||||
|
||||
// On cherche dans org2 où le membre n'est pas
|
||||
Optional<MembreOrganisation> result = membreOrgRepository.findByMembreIdAndOrganisationId(
|
||||
membre.getId(), org2.getId());
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findById retourne null pour UUID inexistant")
|
||||
void findById_inexistant_retourneNull() {
|
||||
MembreOrganisation result = membreOrgRepository.findById(UUID.randomUUID());
|
||||
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("persist et findById fonctionnent correctement")
|
||||
void persistAndFindById_retourneLienPersisteCorrectement() {
|
||||
Membre membre = persistMembre();
|
||||
Organisation org = persistOrganisation();
|
||||
MembreOrganisation mo = persistLien(membre, org);
|
||||
|
||||
MembreOrganisation found = membreOrgRepository.findById(mo.getId());
|
||||
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getStatutMembre()).isEqualTo(StatutMembre.ACTIF);
|
||||
assertThat(found.getDateAdhesion()).isEqualTo(LocalDate.now());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isActif retourne true pour statut ACTIF et actif=true")
|
||||
void isActif_statutActifEtActif_retourneTrue() {
|
||||
Membre membre = persistMembre();
|
||||
Organisation org = persistOrganisation();
|
||||
MembreOrganisation mo = persistLien(membre, org);
|
||||
mo.setStatutMembre(StatutMembre.ACTIF);
|
||||
mo.setActif(true);
|
||||
|
||||
assertThat(mo.isActif()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("isActif retourne false pour statut EN_ATTENTE_VALIDATION")
|
||||
void isActif_statutEnAttente_retourneFalse() {
|
||||
Membre membre = persistMembre();
|
||||
Organisation org = persistOrganisation();
|
||||
MembreOrganisation mo = persistLien(membre, org);
|
||||
mo.setStatutMembre(StatutMembre.EN_ATTENTE_VALIDATION);
|
||||
|
||||
assertThat(mo.isActif()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("peutDemanderAide retourne true pour statut ACTIF")
|
||||
void peutDemanderAide_statutActif_retourneTrue() {
|
||||
Membre membre = persistMembre();
|
||||
Organisation org = persistOrganisation();
|
||||
MembreOrganisation mo = persistLien(membre, org);
|
||||
mo.setStatutMembre(StatutMembre.ACTIF);
|
||||
|
||||
assertThat(mo.peutDemanderAide()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("peutDemanderAide retourne false pour statut EN_ATTENTE_VALIDATION")
|
||||
void peutDemanderAide_statutEnAttente_retourneFalse() {
|
||||
Membre membre = persistMembre();
|
||||
Organisation org = persistOrganisation();
|
||||
MembreOrganisation mo = persistLien(membre, org);
|
||||
mo.setStatutMembre(StatutMembre.EN_ATTENTE_VALIDATION);
|
||||
|
||||
assertThat(mo.peutDemanderAide()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,52 @@ class MembreRepositoryTest {
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("rechercheAvancee avec recherche non null couvre la branche recherche != null")
|
||||
void rechercheAvancee_withRecherche_returnsList() {
|
||||
Membre m = newMembre(UUID.randomUUID().toString());
|
||||
membreRepository.persist(m);
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.rechercheAvancee("Prénom", null, null, null, page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("rechercheAvancee avec sort non null couvre la branche sort != null")
|
||||
void rechercheAvancee_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("nom", Sort.Direction.Ascending);
|
||||
List<Membre> list = membreRepository.rechercheAvancee(null, null, null, null, page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("rechercheAvancee sans actif couvre la branche actif == null")
|
||||
void rechercheAvancee_nullActif_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.rechercheAvancee(null, null, null, null, page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
// ── Branch coverage manquantes ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* L236 + L251 branch manquante : recherche != null mais isEmpty() → false
|
||||
* → `if (recherche != null && !recherche.isEmpty())` → false (recherche est "")
|
||||
*/
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("rechercheAvancee avec recherche vide (empty string) couvre la branche isEmpty")
|
||||
void rechercheAvancee_emptyRecherche_treatedAsNoFilter() {
|
||||
Page page = new Page(0, 10);
|
||||
// recherche = "" → !recherche.isEmpty() est false → branche false du && → pas filtré
|
||||
List<Membre> list = membreRepository.rechercheAvancee("", null, null, null, page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("listAll et count cohérents")
|
||||
@@ -135,4 +181,285 @@ class MembreRepositoryTest {
|
||||
long count = membreRepository.count();
|
||||
assertThat((long) all.size()).isEqualTo(count);
|
||||
}
|
||||
|
||||
// ── Branches manquantes ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findAllActifs(Page, Sort): sort non-null → ORDER BY (branche sort != null)")
|
||||
void findAllActifs_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("nom", Sort.Direction.Ascending);
|
||||
List<Membre> list = membreRepository.findAllActifs(page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findAllActifs(Page, Sort): sort null → pas de ORDER BY (branche sort null)")
|
||||
void findAllActifs_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findAllActifs(page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByNomOrPrenom(Page,Sort): sort non-null → ORDER BY (branche sort != null)")
|
||||
void findByNomOrPrenom_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("nom", Sort.Direction.Descending);
|
||||
List<Membre> list = membreRepository.findByNomOrPrenom("Test", page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByNomOrPrenom(Page,Sort): sort null → pas de ORDER BY")
|
||||
void findByNomOrPrenom_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findByNomOrPrenom("Test", page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findDistinctByOrganisationIdIn: null → retourne liste vide")
|
||||
void findDistinctByOrganisationIdIn_null_returnsEmpty() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findDistinctByOrganisationIdIn(null, page, null);
|
||||
assertThat(list).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findDistinctByOrganisationIdIn: vide → retourne liste vide")
|
||||
void findDistinctByOrganisationIdIn_empty_returnsEmpty() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findDistinctByOrganisationIdIn(java.util.Set.of(), page, null);
|
||||
assertThat(list).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findDistinctByOrganisationIdIn: ids valides + sort non-null → exécute la requête")
|
||||
void findDistinctByOrganisationIdIn_withIds_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("nom", Sort.Direction.Ascending);
|
||||
// ids non existants → liste vide, mais code exécuté
|
||||
List<Membre> list = membreRepository.findDistinctByOrganisationIdIn(
|
||||
java.util.Set.of(UUID.randomUUID()), page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findDistinctByOrganisationIdIn: ids valides + sort null → exécute la requête")
|
||||
void findDistinctByOrganisationIdIn_withIds_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findDistinctByOrganisationIdIn(
|
||||
java.util.Set.of(UUID.randomUUID()), page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countDistinctByOrganisationIdIn: null → retourne 0L")
|
||||
void countDistinctByOrganisationIdIn_null_returnsZero() {
|
||||
long count = membreRepository.countDistinctByOrganisationIdIn(null);
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countDistinctByOrganisationIdIn: vide → retourne 0L")
|
||||
void countDistinctByOrganisationIdIn_empty_returnsZero() {
|
||||
long count = membreRepository.countDistinctByOrganisationIdIn(java.util.Set.of());
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countDistinctByOrganisationIdIn: ids valides → exécute la requête")
|
||||
void countDistinctByOrganisationIdIn_withIds_executesQuery() {
|
||||
long count = membreRepository.countDistinctByOrganisationIdIn(java.util.Set.of(UUID.randomUUID()));
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countActifsDistinctByOrganisationIdIn: null → retourne 0L")
|
||||
void countActifsDistinctByOrganisationIdIn_null_returnsZero() {
|
||||
long count = membreRepository.countActifsDistinctByOrganisationIdIn(null);
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countActifsDistinctByOrganisationIdIn: vide → retourne 0L")
|
||||
void countActifsDistinctByOrganisationIdIn_empty_returnsZero() {
|
||||
long count = membreRepository.countActifsDistinctByOrganisationIdIn(java.util.Set.of());
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countActifsDistinctByOrganisationIdIn: ids valides → exécute la requête")
|
||||
void countActifsDistinctByOrganisationIdIn_withIds_executesQuery() {
|
||||
long count = membreRepository.countActifsDistinctByOrganisationIdIn(java.util.Set.of(UUID.randomUUID()));
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByNomOrPrenomAndOrganisationIdIn: null → retourne liste vide")
|
||||
void findByNomOrPrenomAndOrganisationIdIn_null_returnsEmpty() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findByNomOrPrenomAndOrganisationIdIn("Test", null, page, null);
|
||||
assertThat(list).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByNomOrPrenomAndOrganisationIdIn: vide → retourne liste vide")
|
||||
void findByNomOrPrenomAndOrganisationIdIn_empty_returnsEmpty() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findByNomOrPrenomAndOrganisationIdIn("Test", java.util.Set.of(), page, null);
|
||||
assertThat(list).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByNomOrPrenomAndOrganisationIdIn: ids valides + sort non-null → exécute la requête")
|
||||
void findByNomOrPrenomAndOrganisationIdIn_withIds_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("nom", Sort.Direction.Ascending);
|
||||
List<Membre> list = membreRepository.findByNomOrPrenomAndOrganisationIdIn(
|
||||
"Test", java.util.Set.of(UUID.randomUUID()), page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByNomOrPrenomAndOrganisationIdIn: ids valides + sort null → exécute la requête")
|
||||
void findByNomOrPrenomAndOrganisationIdIn_withIds_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findByNomOrPrenomAndOrganisationIdIn(
|
||||
"Test", java.util.Set.of(UUID.randomUUID()), page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countNouveauxMembresByOrganisationId: null → retourne 0L")
|
||||
void countNouveauxMembresByOrganisationId_null_returnsZero() {
|
||||
long count = membreRepository.countNouveauxMembresByOrganisationId(LocalDate.now().minusMonths(1), null);
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countNouveauxMembresByOrganisationId: org valide → exécute la requête")
|
||||
void countNouveauxMembresByOrganisationId_valid_executesQuery() {
|
||||
long count = membreRepository.countNouveauxMembresByOrganisationId(LocalDate.now().minusMonths(1), UUID.randomUUID());
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countNouveauxMembresByOrganisationIdInPeriod: null → retourne 0L")
|
||||
void countNouveauxMembresByOrganisationIdInPeriod_null_returnsZero() {
|
||||
long count = membreRepository.countNouveauxMembresByOrganisationIdInPeriod(
|
||||
LocalDate.now().minusMonths(1), LocalDate.now(), null);
|
||||
assertThat(count).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countNouveauxMembresByOrganisationIdInPeriod: org valide → exécute la requête")
|
||||
void countNouveauxMembresByOrganisationIdInPeriod_valid_executesQuery() {
|
||||
long count = membreRepository.countNouveauxMembresByOrganisationIdInPeriod(
|
||||
LocalDate.now().minusMonths(1), LocalDate.now(), UUID.randomUUID());
|
||||
assertThat(count).isGreaterThanOrEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByStatut: sort non-null → ORDER BY (branche sort != null)")
|
||||
void findByStatut_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("nom", Sort.Direction.Ascending);
|
||||
List<Membre> list = membreRepository.findByStatut(true, page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByStatut: sort null → pas de ORDER BY")
|
||||
void findByStatut_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findByStatut(false, page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByTrancheAge: sort non-null → ORDER BY (branche sort != null)")
|
||||
void findByTrancheAge_withSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("dateNaissance", Sort.Direction.Ascending);
|
||||
List<Membre> list = membreRepository.findByTrancheAge(20, 40, page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByTrancheAge: sort null → pas de ORDER BY")
|
||||
void findByTrancheAge_nullSort_returnsList() {
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findByTrancheAge(20, 40, page, null);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy: colonnes multiples couvre la branche i>0 (via findAllActifs)")
|
||||
void buildOrderBy_multipleColumns_coversIPlusBranch() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("nom", Sort.Direction.Descending).and("prenom", Sort.Direction.Ascending);
|
||||
List<Membre> list = membreRepository.findAllActifs(page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy: direction ASC couvre la branche Ascending (via findAllActifs)")
|
||||
void buildOrderBy_ascendingDirection_coversAscBranch() {
|
||||
Page page = new Page(0, 10);
|
||||
Sort sort = Sort.by("nom", Sort.Direction.Ascending);
|
||||
List<Membre> list = membreRepository.findAllActifs(page, sort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByKeycloakUserId: string non-UUID → retourne empty (catch IllegalArgumentException)")
|
||||
void findByKeycloakUserId_nonUuid_returnsEmpty() {
|
||||
Optional<Membre> found = membreRepository.findByKeycloakUserId("not-a-uuid");
|
||||
assertThat(found).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("buildOrderBy: sort non-null colonnes vides → fallback m.id (branche isEmpty L265)")
|
||||
void buildOrderBy_sortNonNullEmptyColumns_fallback() throws Exception {
|
||||
// Sort non-null avec liste colonnes vide → L265: isEmpty() = true → fallback "m.id"
|
||||
java.lang.reflect.Constructor<Sort> ctor = Sort.class.getDeclaredConstructor();
|
||||
ctor.setAccessible(true);
|
||||
Sort emptySort = ctor.newInstance();
|
||||
|
||||
Page page = new Page(0, 10);
|
||||
List<Membre> list = membreRepository.findAllActifs(page, emptySort);
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,12 @@ class MembreRoleRepositoryTest {
|
||||
List<MembreRole> list = membreRoleRepository.findByRoleId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreAndRole retourne null pour des UUIDs inexistants")
|
||||
void findByMembreAndRole_inexistant_returnsNull() {
|
||||
MembreRole result = membreRoleRepository.findByMembreAndRole(UUID.randomUUID(), UUID.randomUUID());
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.lions.unionflow.server.entity.MembreSuivi;
|
||||
|
||||
@QuarkusTest
|
||||
class MembreSuiviRepositoryTest {
|
||||
|
||||
@Inject
|
||||
MembreSuiviRepository membreSuiviRepository;
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByFollowerAndSuivi retourne empty pour des UUIDs inexistants")
|
||||
void findByFollowerAndSuivi_inexistant_returnsEmpty() {
|
||||
Optional<MembreSuivi> result = membreSuiviRepository.findByFollowerAndSuivi(
|
||||
UUID.randomUUID(), UUID.randomUUID());
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByFollower retourne une liste vide pour un followerId inexistant")
|
||||
void findByFollower_inexistant_returnsEmptyList() {
|
||||
List<MembreSuivi> list = membreSuiviRepository.findByFollower(UUID.randomUUID());
|
||||
assertThat(list).isNotNull().isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.communication.ConversationType;
|
||||
import dev.lions.unionflow.server.api.enums.communication.MessagePriority;
|
||||
import dev.lions.unionflow.server.api.enums.communication.MessageStatus;
|
||||
import dev.lions.unionflow.server.api.enums.communication.MessageType;
|
||||
import dev.lions.unionflow.server.entity.Conversation;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Message;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@QuarkusTest
|
||||
@DisplayName("MessageRepository — tests des méthodes de requête")
|
||||
class MessageRepositoryTest {
|
||||
|
||||
@Inject
|
||||
MessageRepository messageRepository;
|
||||
|
||||
@Inject
|
||||
EntityManager em;
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
private Membre persistMembre() {
|
||||
Membre m = new Membre();
|
||||
m.setNumeroMembre("MSG-MEM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
|
||||
m.setPrenom("Jean");
|
||||
m.setNom("Messagerie");
|
||||
m.setEmail("msg." + UUID.randomUUID() + "@test.com");
|
||||
m.setDateNaissance(LocalDate.of(1990, 1, 1));
|
||||
m.setActif(true);
|
||||
em.persist(m);
|
||||
em.flush();
|
||||
return m;
|
||||
}
|
||||
|
||||
private Conversation persistConversation() {
|
||||
Conversation conv = new Conversation();
|
||||
conv.setName("Conv Test " + UUID.randomUUID().toString().substring(0, 8));
|
||||
conv.setType(ConversationType.GROUP);
|
||||
conv.setActif(true);
|
||||
em.persist(conv);
|
||||
em.flush();
|
||||
return conv;
|
||||
}
|
||||
|
||||
private Message persistMessage(Conversation conv, Membre sender, MessageStatus status) {
|
||||
Message msg = new Message();
|
||||
msg.setConversation(conv);
|
||||
msg.setSender(sender);
|
||||
msg.setSenderName(sender.getPrenom() + " " + sender.getNom());
|
||||
msg.setContent("Contenu test " + UUID.randomUUID());
|
||||
msg.setType(MessageType.INDIVIDUAL);
|
||||
msg.setStatus(status);
|
||||
msg.setPriority(MessagePriority.NORMAL);
|
||||
msg.setIsEdited(false);
|
||||
msg.setIsDeleted(false);
|
||||
msg.setActif(true);
|
||||
em.persist(msg);
|
||||
em.flush();
|
||||
return msg;
|
||||
}
|
||||
|
||||
// ─── Tests ───────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByConversation retourne les messages actifs non supprimés")
|
||||
void findByConversation_retourneMessagesActifs() {
|
||||
Membre sender = persistMembre();
|
||||
Conversation conv = persistConversation();
|
||||
persistMessage(conv, sender, MessageStatus.SENT);
|
||||
persistMessage(conv, sender, MessageStatus.READ);
|
||||
|
||||
List<Message> messages = messageRepository.findByConversation(conv.getId(), 10);
|
||||
|
||||
assertThat(messages).isNotNull();
|
||||
assertThat(messages).hasSizeGreaterThanOrEqualTo(2);
|
||||
messages.forEach(m -> assertThat(m.getIsDeleted()).isFalse());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByConversation respecte la limite de pagination")
|
||||
void findByConversation_respecteLimite() {
|
||||
Membre sender = persistMembre();
|
||||
Conversation conv = persistConversation();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
persistMessage(conv, sender, MessageStatus.SENT);
|
||||
}
|
||||
|
||||
List<Message> messages = messageRepository.findByConversation(conv.getId(), 3);
|
||||
|
||||
assertThat(messages).hasSizeLessThanOrEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByConversation retourne liste vide pour conversation sans messages")
|
||||
void findByConversation_conversationVide_retourneListe() {
|
||||
Conversation conv = persistConversation();
|
||||
|
||||
List<Message> messages = messageRepository.findByConversation(conv.getId(), 10);
|
||||
|
||||
assertThat(messages).isNotNull();
|
||||
assertThat(messages).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByConversation exclut les messages supprimés")
|
||||
void findByConversation_exclutMessagesSupprimés() {
|
||||
Membre sender = persistMembre();
|
||||
Conversation conv = persistConversation();
|
||||
// Message supprimé
|
||||
Message msgSupprime = persistMessage(conv, sender, MessageStatus.SENT);
|
||||
msgSupprime.setIsDeleted(true);
|
||||
em.flush();
|
||||
|
||||
List<Message> messages = messageRepository.findByConversation(conv.getId(), 10);
|
||||
|
||||
assertThat(messages).noneMatch(m -> m.getId().equals(msgSupprime.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countUnreadByConversationAndMember compte les messages SENT et DELIVERED d'autres membres")
|
||||
void countUnreadByConversationAndMember_compteMsgNonLusAutresMembres() {
|
||||
Membre sender = persistMembre();
|
||||
Membre reader = persistMembre();
|
||||
Conversation conv = persistConversation();
|
||||
persistMessage(conv, sender, MessageStatus.SENT);
|
||||
persistMessage(conv, sender, MessageStatus.DELIVERED);
|
||||
persistMessage(conv, sender, MessageStatus.READ); // déjà lu → exclu
|
||||
|
||||
long count = messageRepository.countUnreadByConversationAndMember(conv.getId(), reader.getId());
|
||||
|
||||
assertThat(count).isGreaterThanOrEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countUnreadByConversationAndMember exclut les messages du membre lui-même")
|
||||
void countUnreadByConversationAndMember_excluMessagesPropresMembre() {
|
||||
Membre sender = persistMembre();
|
||||
Conversation conv = persistConversation();
|
||||
persistMessage(conv, sender, MessageStatus.SENT);
|
||||
|
||||
// Le sender lui-même : ses propres messages ne sont pas comptés comme non lus
|
||||
long count = messageRepository.countUnreadByConversationAndMember(conv.getId(), sender.getId());
|
||||
|
||||
assertThat(count).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("countUnreadByConversationAndMember retourne 0 pour conversation vide")
|
||||
void countUnreadByConversationAndMember_conversationVide_retourneZero() {
|
||||
Conversation conv = persistConversation();
|
||||
UUID membreId = UUID.randomUUID();
|
||||
|
||||
long count = messageRepository.countUnreadByConversationAndMember(conv.getId(), membreId);
|
||||
|
||||
assertThat(count).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("markAllAsReadByConversationAndMember marque les messages SENT et DELIVERED en READ")
|
||||
void markAllAsReadByConversationAndMember_marqueLesMessagesEnRead() {
|
||||
Membre sender = persistMembre();
|
||||
Membre reader = persistMembre();
|
||||
Conversation conv = persistConversation();
|
||||
persistMessage(conv, sender, MessageStatus.SENT);
|
||||
persistMessage(conv, sender, MessageStatus.DELIVERED);
|
||||
|
||||
int updated = messageRepository.markAllAsReadByConversationAndMember(conv.getId(), reader.getId());
|
||||
|
||||
assertThat(updated).isGreaterThanOrEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("markAllAsReadByConversationAndMember ne touche pas les messages déjà READ")
|
||||
void markAllAsReadByConversationAndMember_neTouchePasMsgDejaRead() {
|
||||
Membre sender = persistMembre();
|
||||
Membre reader = persistMembre();
|
||||
Conversation conv = persistConversation();
|
||||
persistMessage(conv, sender, MessageStatus.READ); // déjà lu
|
||||
|
||||
int updated = messageRepository.markAllAsReadByConversationAndMember(conv.getId(), reader.getId());
|
||||
|
||||
assertThat(updated).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findLastByConversation retourne le dernier message")
|
||||
void findLastByConversation_retourneDernierMessage() {
|
||||
Membre sender = persistMembre();
|
||||
Conversation conv = persistConversation();
|
||||
// Set explicit dateCreation to guarantee ordering (PrePersist skips if non-null)
|
||||
Message premierMsg = new Message();
|
||||
premierMsg.setConversation(conv);
|
||||
premierMsg.setSender(sender);
|
||||
premierMsg.setSenderName(sender.getPrenom() + " " + sender.getNom());
|
||||
premierMsg.setContent("Premier message");
|
||||
premierMsg.setType(dev.lions.unionflow.server.api.enums.communication.MessageType.INDIVIDUAL);
|
||||
premierMsg.setStatus(MessageStatus.SENT);
|
||||
premierMsg.setPriority(dev.lions.unionflow.server.api.enums.communication.MessagePriority.NORMAL);
|
||||
premierMsg.setIsEdited(false);
|
||||
premierMsg.setIsDeleted(false);
|
||||
premierMsg.setActif(true);
|
||||
premierMsg.setDateCreation(java.time.LocalDateTime.now().minusSeconds(10));
|
||||
em.persist(premierMsg);
|
||||
em.flush();
|
||||
|
||||
Message dernierMsg = persistMessage(conv, sender, MessageStatus.DELIVERED);
|
||||
|
||||
Message last = messageRepository.findLastByConversation(conv.getId());
|
||||
|
||||
assertThat(last).isNotNull();
|
||||
assertThat(last.getId()).isEqualTo(dernierMsg.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findLastByConversation retourne null pour conversation vide")
|
||||
void findLastByConversation_conversationVide_retourneNull() {
|
||||
Conversation conv = persistConversation();
|
||||
|
||||
Message last = messageRepository.findLastByConversation(conv.getId());
|
||||
|
||||
assertThat(last).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findLastByConversation retourne null pour conversation inexistante")
|
||||
void findLastByConversation_conversationInexistante_retourneNull() {
|
||||
Message last = messageRepository.findLastByConversation(UUID.randomUUID());
|
||||
|
||||
assertThat(last).isNull();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.entity.Notification;
|
||||
import dev.lions.unionflow.server.entity.Organisation;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.TestTransaction;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -19,6 +23,58 @@ class NotificationRepositoryTest {
|
||||
@Inject
|
||||
NotificationRepository notificationRepository;
|
||||
|
||||
@Inject
|
||||
MembreRepository membreRepository;
|
||||
|
||||
@Inject
|
||||
OrganisationRepository organisationRepository;
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
private Membre persistMembre() {
|
||||
Membre m = new Membre();
|
||||
m.setNumeroMembre("MEM-N-" + UUID.randomUUID().toString().substring(0, 8));
|
||||
m.setPrenom("Prénom");
|
||||
m.setNom("NotifTest");
|
||||
m.setEmail("mb-notif-" + UUID.randomUUID() + "@test.com");
|
||||
m.setDateNaissance(LocalDate.of(1990, 1, 1));
|
||||
m.setActif(true);
|
||||
membreRepository.persist(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
private Organisation persistOrganisation() {
|
||||
Organisation o = new Organisation();
|
||||
o.setNom("Org Notif Test");
|
||||
o.setTypeOrganisation("ASSOCIATION");
|
||||
o.setStatut("ACTIVE");
|
||||
o.setEmail("org-notif-" + UUID.randomUUID() + "@test.com");
|
||||
o.setActif(true);
|
||||
organisationRepository.persist(o);
|
||||
return o;
|
||||
}
|
||||
|
||||
private Notification persistNotification(Membre membre,
|
||||
Organisation organisation,
|
||||
String type,
|
||||
String statut,
|
||||
String priorite,
|
||||
LocalDateTime dateEnvoiPrevue) {
|
||||
Notification n = new Notification();
|
||||
n.setTypeNotification(type);
|
||||
n.setStatut(statut);
|
||||
n.setPriorite(priorite);
|
||||
n.setMembre(membre);
|
||||
n.setOrganisation(organisation);
|
||||
n.setDateEnvoiPrevue(dateEnvoiPrevue);
|
||||
n.setNombreTentatives(0);
|
||||
n.setActif(true);
|
||||
notificationRepository.persist(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
// ── tests hérités (inchangés) ─────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findById retourne null pour UUID inexistant")
|
||||
@@ -64,4 +120,288 @@ class NotificationRepositoryTest {
|
||||
List<Notification> list = notificationRepository.findNonLuesByMembreId(UUID.randomUUID());
|
||||
assertThat(list).isNotNull();
|
||||
}
|
||||
|
||||
// ── findNotificationById ──────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findNotificationById retrouve la notification persistée")
|
||||
void findNotificationById_found_returnsPresent() {
|
||||
Membre m = persistMembre();
|
||||
Notification n = persistNotification(m, null, "ALERTE", "EN_ATTENTE", "NORMALE",
|
||||
LocalDateTime.now().plusHours(1));
|
||||
|
||||
Optional<Notification> result = notificationRepository.findNotificationById(n.getId());
|
||||
|
||||
assertThat(result).isPresent();
|
||||
assertThat(result.get().getId()).isEqualTo(n.getId());
|
||||
}
|
||||
|
||||
// ── findByMembreId ────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreId retourne liste vide pour membre sans notifications")
|
||||
void findByMembreId_noNotifications_returnsEmpty() {
|
||||
Membre m = persistMembre();
|
||||
|
||||
List<Notification> result = notificationRepository.findByMembreId(m.getId());
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByMembreId retrouve les notifications du membre")
|
||||
void findByMembreId_withNotifications_returnsList() {
|
||||
Membre m = persistMembre();
|
||||
persistNotification(m, null, "INFO", "EN_ATTENTE", "NORMALE", LocalDateTime.now().plusMinutes(5));
|
||||
persistNotification(m, null, "ALERTE", "ENVOYEE", "HAUTE", LocalDateTime.now().minusMinutes(1));
|
||||
|
||||
List<Notification> result = notificationRepository.findByMembreId(m.getId());
|
||||
|
||||
assertThat(result).hasSize(2);
|
||||
}
|
||||
|
||||
// ── findNonLuesByMembreId ─────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findNonLuesByMembreId retourne liste vide pour membre sans notification NON_LUE")
|
||||
void findNonLuesByMembreId_noNonLues_returnsEmpty() {
|
||||
Membre m = persistMembre();
|
||||
persistNotification(m, null, "INFO", "LUE", "NORMALE", LocalDateTime.now().plusMinutes(5));
|
||||
|
||||
List<Notification> result = notificationRepository.findNonLuesByMembreId(m.getId());
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findNonLuesByMembreId retrouve uniquement les notifications NON_LUE du membre")
|
||||
void findNonLuesByMembreId_withNonLues_returnsList() {
|
||||
Membre m = persistMembre();
|
||||
persistNotification(m, null, "ALERTE", "NON_LUE", "HAUTE", LocalDateTime.now().plusMinutes(10));
|
||||
persistNotification(m, null, "INFO", "LUE", "NORMALE", LocalDateTime.now().minusMinutes(5));
|
||||
|
||||
List<Notification> result = notificationRepository.findNonLuesByMembreId(m.getId());
|
||||
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result.get(0).getStatut()).isEqualTo("NON_LUE");
|
||||
}
|
||||
|
||||
// ── findByOrganisationId ──────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationId retourne liste vide pour organisation inexistante")
|
||||
void findByOrganisationId_inexistant_returnsEmpty() {
|
||||
List<Notification> result = notificationRepository.findByOrganisationId(UUID.randomUUID());
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByOrganisationId retrouve les notifications de l'organisation")
|
||||
void findByOrganisationId_withNotifications_returnsList() {
|
||||
Organisation o = persistOrganisation();
|
||||
persistNotification(null, o, "RAPPEL", "EN_ATTENTE", "NORMALE", LocalDateTime.now().plusHours(2));
|
||||
persistNotification(null, o, "INFO", "ENVOYEE", "BASSE", LocalDateTime.now().minusHours(1));
|
||||
|
||||
List<Notification> result = notificationRepository.findByOrganisationId(o.getId());
|
||||
|
||||
assertThat(result).hasSize(2);
|
||||
}
|
||||
|
||||
// ── findByType ────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByType retourne liste vide pour type inexistant")
|
||||
void findByType_inexistant_returnsEmpty() {
|
||||
List<Notification> result = notificationRepository.findByType("TYPE_INEXISTANT_" + UUID.randomUUID());
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByType retrouve les notifications du type demandé")
|
||||
void findByType_withMatch_returnsList() {
|
||||
Membre m = persistMembre();
|
||||
String uniqueType = "TYPE_" + UUID.randomUUID().toString().substring(0, 8);
|
||||
persistNotification(m, null, uniqueType, "EN_ATTENTE", "NORMALE", LocalDateTime.now().plusMinutes(30));
|
||||
|
||||
List<Notification> result = notificationRepository.findByType(uniqueType);
|
||||
|
||||
assertThat(result).isNotEmpty();
|
||||
assertThat(result).allMatch(n -> n.getTypeNotification().equals(uniqueType));
|
||||
}
|
||||
|
||||
// ── findByStatut ──────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByStatut retourne liste vide pour statut inexistant")
|
||||
void findByStatut_inexistant_returnsEmpty() {
|
||||
List<Notification> result = notificationRepository.findByStatut("STATUT_INEXISTANT_" + UUID.randomUUID());
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByStatut retrouve les notifications du statut demandé")
|
||||
void findByStatut_withMatch_returnsList() {
|
||||
Membre m = persistMembre();
|
||||
persistNotification(m, null, "ALERTE", "PROGRAMMEE", "HAUTE", LocalDateTime.now().plusDays(1));
|
||||
|
||||
List<Notification> result = notificationRepository.findByStatut("PROGRAMMEE");
|
||||
|
||||
assertThat(result).isNotEmpty();
|
||||
assertThat(result).allMatch(n -> "PROGRAMMEE".equals(n.getStatut()));
|
||||
}
|
||||
|
||||
// ── findByPriorite ────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByPriorite retourne liste vide pour priorité inexistante")
|
||||
void findByPriorite_inexistant_returnsEmpty() {
|
||||
List<Notification> result = notificationRepository.findByPriorite("PRIORITE_INCONNUE_" + UUID.randomUUID());
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findByPriorite retrouve les notifications de la priorité demandée")
|
||||
void findByPriorite_withMatch_returnsList() {
|
||||
Membre m = persistMembre();
|
||||
persistNotification(m, null, "INFO", "EN_ATTENTE", "CRITIQUE", LocalDateTime.now().plusHours(1));
|
||||
|
||||
List<Notification> result = notificationRepository.findByPriorite("CRITIQUE");
|
||||
|
||||
assertThat(result).isNotEmpty();
|
||||
assertThat(result).allMatch(n -> "CRITIQUE".equals(n.getPriorite()));
|
||||
}
|
||||
|
||||
// ── findEnAttenteEnvoi ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEnAttenteEnvoi retourne liste (éventuellement vide)")
|
||||
void findEnAttenteEnvoi_returnsNonNullList() {
|
||||
List<Notification> result = notificationRepository.findEnAttenteEnvoi();
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEnAttenteEnvoi inclut les notifications EN_ATTENTE dont la date est passée")
|
||||
void findEnAttenteEnvoi_withEnAttentePassee_includesNotification() {
|
||||
Membre m = persistMembre();
|
||||
Notification n = persistNotification(m, null, "RAPPEL", "EN_ATTENTE", "NORMALE",
|
||||
LocalDateTime.now().minusMinutes(5));
|
||||
|
||||
List<Notification> result = notificationRepository.findEnAttenteEnvoi();
|
||||
|
||||
assertThat(result).anyMatch(notif -> notif.getId().equals(n.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEnAttenteEnvoi inclut les notifications PROGRAMMEE dont la date est passée")
|
||||
void findEnAttenteEnvoi_withProgrammeePassee_includesNotification() {
|
||||
Membre m = persistMembre();
|
||||
Notification n = persistNotification(m, null, "INFO", "PROGRAMMEE", "BASSE",
|
||||
LocalDateTime.now().minusHours(1));
|
||||
|
||||
List<Notification> result = notificationRepository.findEnAttenteEnvoi();
|
||||
|
||||
assertThat(result).anyMatch(notif -> notif.getId().equals(n.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEnAttenteEnvoi exclut les notifications dont la date d'envoi est dans le futur")
|
||||
void findEnAttenteEnvoi_futurDate_excluded() {
|
||||
Membre m = persistMembre();
|
||||
Notification n = persistNotification(m, null, "RAPPEL", "EN_ATTENTE", "NORMALE",
|
||||
LocalDateTime.now().plusHours(24));
|
||||
|
||||
List<Notification> result = notificationRepository.findEnAttenteEnvoi();
|
||||
|
||||
assertThat(result).noneMatch(notif -> notif.getId().equals(n.getId()));
|
||||
}
|
||||
|
||||
// ── findEchoueesRetentables ───────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEchoueesRetentables retourne liste (éventuellement vide)")
|
||||
void findEchoueesRetentables_returnsNonNullList() {
|
||||
List<Notification> result = notificationRepository.findEchoueesRetentables();
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEchoueesRetentables inclut les notifications ECHEC_ENVOI avec moins de 5 tentatives")
|
||||
void findEchoueesRetentables_withEchecEnvoi_includesNotification() {
|
||||
Membre m = persistMembre();
|
||||
Notification n = new Notification();
|
||||
n.setTypeNotification("ALERTE");
|
||||
n.setStatut("ECHEC_ENVOI");
|
||||
n.setPriorite("HAUTE");
|
||||
n.setNombreTentatives(2);
|
||||
n.setDateEnvoiPrevue(LocalDateTime.now().minusHours(1));
|
||||
n.setMembre(m);
|
||||
n.setActif(true);
|
||||
notificationRepository.persist(n);
|
||||
|
||||
List<Notification> result = notificationRepository.findEchoueesRetentables();
|
||||
|
||||
assertThat(result).anyMatch(notif -> notif.getId().equals(n.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEchoueesRetentables inclut les notifications ERREUR_TECHNIQUE avec moins de 5 tentatives")
|
||||
void findEchoueesRetentables_withErreurTechnique_includesNotification() {
|
||||
Membre m = persistMembre();
|
||||
Notification n = new Notification();
|
||||
n.setTypeNotification("INFO");
|
||||
n.setStatut("ERREUR_TECHNIQUE");
|
||||
n.setPriorite("NORMALE");
|
||||
n.setNombreTentatives(1);
|
||||
n.setDateEnvoiPrevue(LocalDateTime.now().minusMinutes(30));
|
||||
n.setMembre(m);
|
||||
n.setActif(true);
|
||||
notificationRepository.persist(n);
|
||||
|
||||
List<Notification> result = notificationRepository.findEchoueesRetentables();
|
||||
|
||||
assertThat(result).anyMatch(notif -> notif.getId().equals(n.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestTransaction
|
||||
@DisplayName("findEchoueesRetentables exclut les notifications avec 5 tentatives ou plus")
|
||||
void findEchoueesRetentables_maxTentativesReached_excluded() {
|
||||
Membre m = persistMembre();
|
||||
Notification n = new Notification();
|
||||
n.setTypeNotification("RAPPEL");
|
||||
n.setStatut("ECHEC_ENVOI");
|
||||
n.setPriorite("NORMALE");
|
||||
n.setNombreTentatives(5);
|
||||
n.setDateEnvoiPrevue(LocalDateTime.now().minusHours(2));
|
||||
n.setMembre(m);
|
||||
n.setActif(true);
|
||||
notificationRepository.persist(n);
|
||||
|
||||
List<Notification> result = notificationRepository.findEchoueesRetentables();
|
||||
|
||||
assertThat(result).noneMatch(notif -> notif.getId().equals(n.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user