diff --git a/src/main/java/dev/lions/unionflow/server/entity/Membre.java b/src/main/java/dev/lions/unionflow/server/entity/Membre.java index d78ba0b..b8a0a4f 100644 --- a/src/main/java/dev/lions/unionflow/server/entity/Membre.java +++ b/src/main/java/dev/lions/unionflow/server/entity/Membre.java @@ -170,9 +170,9 @@ public class Membre extends BaseEntity { // ── Relations ──────────────────────────────────────────────────────────── - /** Adhésions à des organisations */ + /** Adhésions à des organisations — CascadeType.REMOVE exclu intentionnellement pour conserver l'historique */ @JsonIgnore - @OneToMany(mappedBy = "membre", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @OneToMany(mappedBy = "membre", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY) @Builder.Default private List membresOrganisations = new ArrayList<>(); @@ -194,7 +194,7 @@ public class Membre extends BaseEntity { // ── Méthodes métier ─────────────────────────────────────────────────────── public String getNomComplet() { - return prenom + " " + nom; + return (prenom != null ? prenom : "") + " " + (nom != null ? nom : ""); } public boolean isMajeur() { diff --git a/src/main/java/dev/lions/unionflow/server/resource/AlerteLcbFtResource.java b/src/main/java/dev/lions/unionflow/server/resource/AlerteLcbFtResource.java index 89701d3..af2ad93 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/AlerteLcbFtResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/AlerteLcbFtResource.java @@ -3,6 +3,7 @@ package dev.lions.unionflow.server.resource; import dev.lions.unionflow.server.api.dto.lcbft.AlerteLcbFtResponse; import dev.lions.unionflow.server.entity.AlerteLcbFt; import dev.lions.unionflow.server.repository.AlerteLcbFtRepository; +import dev.lions.unionflow.server.service.AlerteLcbFtService; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.ws.rs.*; @@ -34,6 +35,9 @@ public class AlerteLcbFtResource { @Inject AlerteLcbFtRepository alerteLcbFtRepository; + @Inject + AlerteLcbFtService alerteLcbFtService; + /** * Récupère les alertes LCB-FT avec filtres et pagination. */ @@ -106,25 +110,21 @@ public class AlerteLcbFtResource { @PathParam("id") String id, Map body ) { - AlerteLcbFt alerte = alerteLcbFtRepository.findById(UUID.fromString(id)); - if (alerte == null) { - throw new NotFoundException("Alerte non trouvée"); - } - - alerte.setTraitee(true); - alerte.setDateTraitement(LocalDateTime.now()); + UUID traiteParId = null; String traiteParStr = body.get("traitePar"); if (traiteParStr != null && !traiteParStr.isBlank()) { try { - alerte.setTraitePar(UUID.fromString(traiteParStr)); + traiteParId = UUID.fromString(traiteParStr); } catch (IllegalArgumentException e) { throw new BadRequestException("traitePar doit être un UUID valide"); } } - alerte.setCommentaireTraitement(body.get("commentaire")); - - alerteLcbFtRepository.persist(alerte); + AlerteLcbFt alerte = alerteLcbFtService.traiterAlerte( + UUID.fromString(id), traiteParId, body.get("commentaire")); + if (alerte == null) { + throw new NotFoundException("Alerte non trouvée"); + } return Response.ok(mapToResponse(alerte)).build(); } diff --git a/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java b/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java index 13236ad..4091e1f 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/ComptabiliteResource.java @@ -25,7 +25,7 @@ import org.jboss.logging.Logger; @Path("/api/comptabilite") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) -@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE", "USER" }) +@RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "TRESORIER" }) @Tag(name = "Comptabilité", description = "Gestion comptable : comptes, journaux et écritures comptables") public class ComptabiliteResource { @@ -45,7 +45,7 @@ public class ComptabiliteResource { * @return Compte créé */ @POST - @RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" }) + @RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "TRESORIER" }) @Path("/comptes") public Response creerCompteComptable(@Valid CreateCompteComptableRequest request) { try { @@ -117,7 +117,7 @@ public class ComptabiliteResource { * @return Journal créé */ @POST - @RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "MEMBRE" }) + @RolesAllowed({ "ADMIN", "ADMIN_ORGANISATION", "TRESORIER" }) @Path("/journaux") public Response creerJournalComptable(@Valid CreateJournalComptableRequest request) { try { diff --git a/src/main/java/dev/lions/unionflow/server/resource/DashboardWebSocketEndpoint.java b/src/main/java/dev/lions/unionflow/server/resource/DashboardWebSocketEndpoint.java index 6c6e54f..f642ec1 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/DashboardWebSocketEndpoint.java +++ b/src/main/java/dev/lions/unionflow/server/resource/DashboardWebSocketEndpoint.java @@ -1,5 +1,6 @@ package dev.lions.unionflow.server.resource; +import io.quarkus.security.Authenticated; import io.quarkus.websockets.next.OnClose; import io.quarkus.websockets.next.OnOpen; import io.quarkus.websockets.next.OnTextMessage; @@ -12,6 +13,7 @@ import org.jboss.logging.Logger; * Les clients mobiles et web se connectent ici pour recevoir les mises à jour. * Types de messages supportés : stats_update, new_activity, event_update, notification, pong */ +@Authenticated @WebSocket(path = "/ws/dashboard") public class DashboardWebSocketEndpoint { diff --git a/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java b/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java index e2f66b7..f2c99bb 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/MembreResource.java @@ -514,6 +514,7 @@ public class MembreResource { @GET @Path("/recherche") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR", "SECRETAIRE"}) @Operation(summary = "Rechercher des membres par nom ou prénom") @APIResponse(responseCode = "200", description = "Résultats de la recherche") public Response rechercherMembres( @@ -542,6 +543,7 @@ public class MembreResource { @GET @Path("/stats") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR", "SECRETAIRE"}) @Operation(summary = "Obtenir les statistiques avancées des membres") @APIResponse(responseCode = "200", description = "Statistiques complètes des membres") public Response obtenirStatistiques() { @@ -552,6 +554,7 @@ public class MembreResource { @GET @Path("/autocomplete/villes") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR", "SECRETAIRE"}) @Operation(summary = "Obtenir la liste des villes pour autocomplétion") @APIResponse(responseCode = "200", description = "Liste des villes distinctes") public Response obtenirVilles( @@ -563,6 +566,7 @@ public class MembreResource { @GET @Path("/autocomplete/professions") + @RolesAllowed({"ADMIN", "SUPER_ADMIN", "ADMIN_ORGANISATION", "MODERATEUR", "SECRETAIRE"}) @Operation(summary = "Obtenir la liste des professions pour autocomplétion") @APIResponse(responseCode = "200", description = "Liste des professions distinctes") public Response obtenirProfessions( diff --git a/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java b/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java index 76d7947..ac978a6 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/OrganisationResource.java @@ -141,7 +141,7 @@ public class OrganisationResource { /** Récupère toutes les organisations actives */ @GET - @jakarta.annotation.security.PermitAll // ✅ Accès public pour inscription + @Authenticated @Operation( summary = "Lister les organisations", description = "Récupère la liste des organisations actives avec pagination") diff --git a/src/main/java/dev/lions/unionflow/server/service/AlerteLcbFtService.java b/src/main/java/dev/lions/unionflow/server/service/AlerteLcbFtService.java index 47a8651..5fb6c71 100644 --- a/src/main/java/dev/lions/unionflow/server/service/AlerteLcbFtService.java +++ b/src/main/java/dev/lions/unionflow/server/service/AlerteLcbFtService.java @@ -115,6 +115,23 @@ public class AlerteLcbFtService { } } + /** + * Marque une alerte comme traitée. + * + * @return l'alerte mise à jour, ou {@code null} si elle n'existe pas + */ + @Transactional + public AlerteLcbFt traiterAlerte(UUID alerteId, UUID traiteParId, String commentaire) { + AlerteLcbFt alerte = alerteLcbFtRepository.findById(alerteId); + if (alerte == null) return null; + alerte.setTraitee(true); + alerte.setDateTraitement(LocalDateTime.now()); + alerte.setTraitePar(traiteParId); + alerte.setCommentaireTraitement(commentaire); + alerteLcbFtRepository.persist(alerte); + return alerte; + } + /** * Génère une alerte pour justification manquante. */ diff --git a/src/main/java/dev/lions/unionflow/server/service/devise/DeviseConversionService.java b/src/main/java/dev/lions/unionflow/server/service/devise/DeviseConversionService.java index 1e0827a..020a19f 100644 --- a/src/main/java/dev/lions/unionflow/server/service/devise/DeviseConversionService.java +++ b/src/main/java/dev/lions/unionflow/server/service/devise/DeviseConversionService.java @@ -82,7 +82,7 @@ public class DeviseConversionService { // 2. Inverse exact Optional inverse = repository.trouverExact(cible, source, date); if (inverse.isPresent()) { - return BigDecimal.ONE.divide(inverse.get().getTaux(), 8, RoundingMode.HALF_UP); + return inverserTaux(inverse.get().getTaux(), cible, source, date); } // 3. Pivot via XOF @@ -105,7 +105,7 @@ public class DeviseConversionService { } Optional recentInverse = repository.trouverPlusRecent(cible, source, date); if (recentInverse.isPresent()) { - return BigDecimal.ONE.divide(recentInverse.get().getTaux(), 8, RoundingMode.HALF_UP); + return inverserTaux(recentInverse.get().getTaux(), cible, source, date); } throw new TauxIntrouvableException( @@ -119,7 +119,7 @@ public class DeviseConversionService { Optional inverse = repository.trouverExact(cible, source, date); if (inverse.isPresent()) { - return BigDecimal.ONE.divide(inverse.get().getTaux(), 8, RoundingMode.HALF_UP); + return inverserTaux(inverse.get().getTaux(), cible, source, date); } Optional recent = repository.trouverPlusRecent(source, cible, date); @@ -127,13 +127,22 @@ public class DeviseConversionService { Optional recentInverse = repository.trouverPlusRecent(cible, source, date); if (recentInverse.isPresent()) { - return BigDecimal.ONE.divide(recentInverse.get().getTaux(), 8, RoundingMode.HALF_UP); + return inverserTaux(recentInverse.get().getTaux(), cible, source, date); } throw new TauxIntrouvableException( "Pivot impossible : " + source + "→" + cible + " (date " + date + ")"); } + /** Calcule 1/taux en levant TauxIntrouvableException si taux est zéro. */ + private BigDecimal inverserTaux(BigDecimal taux, Devise from, Devise to, LocalDate date) { + if (taux.compareTo(BigDecimal.ZERO) == 0) { + throw new TauxIntrouvableException( + "Taux " + from + "→" + to + " est zéro (date: " + date + ")"); + } + return BigDecimal.ONE.divide(taux, 8, RoundingMode.HALF_UP); + } + /** Vide le cache (utilisé après import batch de nouveaux taux). */ public void invaliderCache() { cache.clear(); diff --git a/src/main/resources/db/migration/V39__PostgreSQL_RLS_Tenant_Isolation.sql b/src/main/resources/db/migration/V39__PostgreSQL_RLS_Tenant_Isolation.sql index becc01e..33d3a8d 100644 --- a/src/main/resources/db/migration/V39__PostgreSQL_RLS_Tenant_Isolation.sql +++ b/src/main/resources/db/migration/V39__PostgreSQL_RLS_Tenant_Isolation.sql @@ -110,7 +110,7 @@ DO $$ BEGIN USING ( EXISTS ( SELECT 1 FROM membres_organisations mo - WHERE mo.utilisateur_id = kyc_dossier.membre_id + WHERE mo.membre_id = kyc_dossier.membre_id AND mo.organisation_id = current_setting('app.current_org_id', true)::uuid AND mo.actif = true ) diff --git a/src/main/resources/db/migration/V45__P0_2026_04_25_Roles_President_Controleur_Audit_Trail.sql b/src/main/resources/db/migration/V45__P0_2026_04_25_Roles_President_Controleur_Audit_Trail.sql index e527fa1..01b8266 100644 --- a/src/main/resources/db/migration/V45__P0_2026_04_25_Roles_President_Controleur_Audit_Trail.sql +++ b/src/main/resources/db/migration/V45__P0_2026_04_25_Roles_President_Controleur_Audit_Trail.sql @@ -8,26 +8,26 @@ -- 1. Rôles standards manquants pour gouvernance OHADA / SoD -- Insertion dans roles existant (V13__Seed_Standard_Roles.sql avait posé la base) -INSERT INTO roles (id, nom, description, categorie, niveau_hierarchie, actif, cree_le, version) +INSERT INTO roles (id, nom, code, libelle, description, categorie, niveau_hierarchique, type_role, actif, date_creation, date_modification, cree_par, modifie_par, version) VALUES - (gen_random_uuid(), 'PRESIDENT', + (gen_random_uuid(), 'PRESIDENT', 'PRESIDENT', 'Président', 'Président de l''organisation : représentant légal, signataire PV AG/CA, convoque les instances. Distinct d''ADMIN_ORGANISATION (rôle technique).', - 'GOUVERNANCE', 1, TRUE, CURRENT_TIMESTAMP, 0), - (gen_random_uuid(), 'VICE_PRESIDENT', + 'GOUVERNANCE', 1, 'SYSTEME', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0), + (gen_random_uuid(), 'VICE_PRESIDENT', 'VICE_PRESIDENT', 'Vice-Président', 'Vice-président : suppléance statutaire du président (OHADA AUDSCGIE).', - 'GOUVERNANCE', 2, TRUE, CURRENT_TIMESTAMP, 0), - (gen_random_uuid(), 'CONTROLEUR_INTERNE', + 'GOUVERNANCE', 2, 'SYSTEME', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0), + (gen_random_uuid(), 'CONTROLEUR_INTERNE', 'CONTROLEUR_INTERNE', 'Contrôleur Interne', 'Contrôleur interne : obligatoire SFD UMOA (BCEAO Circulaire 03-2017/CB/C). Supervise risques, conformité, audit interne.', - 'CONTROLE', 3, TRUE, CURRENT_TIMESTAMP, 0), - (gen_random_uuid(), 'ANIMATEUR_ZONE', + 'CONTROLE', 3, 'SYSTEME', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0), + (gen_random_uuid(), 'ANIMATEUR_ZONE', 'ANIMATEUR_ZONE', 'Animateur de Zone', 'Animateur de zone / délégué régional : enquête sociale demandes d''aide, lien terrain.', - 'OPERATIONNEL', 4, TRUE, CURRENT_TIMESTAMP, 0), - (gen_random_uuid(), 'SECRETAIRE_ADJOINT', + 'OPERATIONNEL', 4, 'SYSTEME', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0), + (gen_random_uuid(), 'SECRETAIRE_ADJOINT', 'SECRETAIRE_ADJOINT', 'Secrétaire Adjoint', 'Secrétaire adjoint : suppléance secrétaire général.', - 'GOUVERNANCE', 5, TRUE, CURRENT_TIMESTAMP, 0), - (gen_random_uuid(), 'TRESORIER_ADJOINT', + 'GOUVERNANCE', 5, 'SYSTEME', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0), + (gen_random_uuid(), 'TRESORIER_ADJOINT', 'TRESORIER_ADJOINT', 'Trésorier Adjoint', 'Trésorier adjoint : suppléance trésorier (séparation des pouvoirs maintenue).', - 'GOUVERNANCE', 5, TRUE, CURRENT_TIMESTAMP, 0) + 'GOUVERNANCE', 5, 'SYSTEME', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'system', 'system', 0) ON CONFLICT (nom) DO NOTHING; -- 2. Audit trail enrichi (P0-NEW-19) — SYSCOHADA + AUDSCGIE OHADA diff --git a/src/main/resources/db/migration/V46__P1_2026_04_25_PV_OHADA_DemandeAide_v2_Donateurs_RoleDelegation.sql b/src/main/resources/db/migration/V46__P1_2026_04_25_PV_OHADA_DemandeAide_v2_Donateurs_RoleDelegation.sql index 704ef82..63a43ab 100644 --- a/src/main/resources/db/migration/V46__P1_2026_04_25_PV_OHADA_DemandeAide_v2_Donateurs_RoleDelegation.sql +++ b/src/main/resources/db/migration/V46__P1_2026_04_25_PV_OHADA_DemandeAide_v2_Donateurs_RoleDelegation.sql @@ -92,11 +92,30 @@ ALTER TABLE demandes_aide ADD COLUMN IF NOT EXISTS reference_paiement VARCHAR(10 CREATE INDEX IF NOT EXISTS idx_demande_aide_etape ON demandes_aide (etape); CREATE INDEX IF NOT EXISTS idx_demande_aide_animateur ON demandes_aide (animateur_zone_id) WHERE animateur_zone_id IS NOT NULL; --- Plafonds : extension types_aide existants -ALTER TABLE types_aide ADD COLUMN IF NOT EXISTS plafond_annuel_membre NUMERIC(15,2); -ALTER TABLE types_aide ADD COLUMN IF NOT EXISTS plafond_enveloppe_annuelle NUMERIC(15,2); -ALTER TABLE types_aide ADD COLUMN IF NOT EXISTS delai_max_traitement_jours INTEGER NOT NULL DEFAULT 30; -ALTER TABLE types_aide ADD COLUMN IF NOT EXISTS justificatifs_requis JSONB; -- liste de strings +-- Plafonds : table types_aide (n'existait pas avant V46 — type_aide était inline VARCHAR dans demandes_aide) +CREATE TABLE IF NOT EXISTS types_aide ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + organisation_id UUID, -- null = type global plateforme + code VARCHAR(50) NOT NULL UNIQUE, + libelle VARCHAR(200) NOT NULL, + description TEXT, + + -- Plafonds (P1-NEW-3) + plafond_annuel_membre NUMERIC(15,2), + plafond_enveloppe_annuelle NUMERIC(15,2), + delai_max_traitement_jours INTEGER NOT NULL DEFAULT 30, + justificatifs_requis JSONB, -- liste de strings + + cree_le TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + cree_par UUID, + modifie_le TIMESTAMP, + modifie_par UUID, + version BIGINT NOT NULL DEFAULT 0, + actif BOOLEAN NOT NULL DEFAULT TRUE +); + +CREATE INDEX IF NOT EXISTS idx_types_aide_org ON types_aide (organisation_id) WHERE organisation_id IS NOT NULL; +CREATE INDEX IF NOT EXISTS idx_types_aide_code ON types_aide (code); COMMENT ON COLUMN demandes_aide.etape IS '5 étapes du workflow v2 : DEPOSE → ENQUETE (animateur zone) → AVIS_COMITE (commission solidarité) ' diff --git a/src/main/resources/db/migration/V49__P2_2026_04_25_Multi_Devise_Diaspora.sql b/src/main/resources/db/migration/V49__P2_2026_04_25_Multi_Devise_Diaspora.sql index 46275c7..4809330 100644 --- a/src/main/resources/db/migration/V49__P2_2026_04_25_Multi_Devise_Diaspora.sql +++ b/src/main/resources/db/migration/V49__P2_2026_04_25_Multi_Devise_Diaspora.sql @@ -50,7 +50,7 @@ VALUES ON CONFLICT (devise_source, devise_cible, date_validite) DO NOTHING; -- ── Extension Membre (diaspora) ───────────────────────────────────────────── -ALTER TABLE membres +ALTER TABLE utilisateurs ADD COLUMN IF NOT EXISTS pays_residence VARCHAR(3), ADD COLUMN IF NOT EXISTS numero_passeport VARCHAR(50), ADD COLUMN IF NOT EXISTS numero_fiscal_etranger VARCHAR(50), @@ -58,27 +58,27 @@ ALTER TABLE membres ADD COLUMN IF NOT EXISTS devise_preferee VARCHAR(3) NOT NULL DEFAULT 'XOF'; CREATE INDEX IF NOT EXISTS idx_membre_diaspora - ON membres (est_diaspora) WHERE est_diaspora = TRUE; + ON utilisateurs (est_diaspora) WHERE est_diaspora = TRUE; -COMMENT ON COLUMN membres.pays_residence IS +COMMENT ON COLUMN utilisateurs.pays_residence IS 'ISO-3 (FRA, USA, CAN, GBR...). NULL = résident UEMOA. Différent de nationalite.'; -COMMENT ON COLUMN membres.numero_passeport IS +COMMENT ON COLUMN utilisateurs.numero_passeport IS 'Passeport pour non-résidents (CNI insuffisante). Vérification ARTCI.'; -COMMENT ON COLUMN membres.numero_fiscal_etranger IS +COMMENT ON COLUMN utilisateurs.numero_fiscal_etranger IS 'NIF/SSN/SIN — pour reporting fiscal accord bilatéral CI-pays résidence.'; -- ── Extension KycDossier (non-résident) ───────────────────────────────────── -ALTER TABLE kyc_dossiers +ALTER TABLE kyc_dossier ADD COLUMN IF NOT EXISTS pays_origine_fonds VARCHAR(3), ADD COLUMN IF NOT EXISTS justificatif_residence_etrangere VARCHAR(500), ADD COLUMN IF NOT EXISTS niveau_due_diligence VARCHAR(20) NOT NULL DEFAULT 'STANDARD' CHECK (niveau_due_diligence IN ('SIMPLIFIE', 'STANDARD', 'RENFORCE')); CREATE INDEX IF NOT EXISTS idx_kyc_due_diligence - ON kyc_dossiers (niveau_due_diligence) + ON kyc_dossier (niveau_due_diligence) WHERE niveau_due_diligence = 'RENFORCE'; -COMMENT ON COLUMN kyc_dossiers.niveau_due_diligence IS +COMMENT ON COLUMN kyc_dossier.niveau_due_diligence IS 'Instr. BCEAO 001-03-2025 : RENFORCE pour non-résidents, PEP, et risque pays grey-list FATF'; -COMMENT ON COLUMN kyc_dossiers.pays_origine_fonds IS +COMMENT ON COLUMN kyc_dossier.pays_origine_fonds IS 'ISO-3 — origine des fonds pour transferts internationaux (anti-blanchiment).'; diff --git a/src/test/java/dev/lions/unionflow/server/resource/WaveRedirectResourceMockEnabledTest.java b/src/test/java/dev/lions/unionflow/server/resource/WaveRedirectResourceMockEnabledTest.java index 8639402..2e861c2 100644 --- a/src/test/java/dev/lions/unionflow/server/resource/WaveRedirectResourceMockEnabledTest.java +++ b/src/test/java/dev/lions/unionflow/server/resource/WaveRedirectResourceMockEnabledTest.java @@ -154,12 +154,10 @@ class WaveRedirectResourceMockEnabledTest { cotisation.setMontantDu(new BigDecimal("5000")); cotisation.setStatut("EN_ATTENTE"); - EntityManager em = mock(EntityManager.class); when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention); + when(entityManager.find(eq(Cotisation.class), eq(cotisationId))).thenReturn(cotisation); + when(entityManager.merge(any(Cotisation.class))).thenReturn(cotisation); doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class)); - when(intentionPaiementRepository.getEntityManager()).thenReturn(em); - when(em.find(eq(Cotisation.class), eq(cotisationId))).thenReturn(cotisation); - when(em.merge(any(Cotisation.class))).thenReturn(cotisation); given() .redirects().follow(false) @@ -170,7 +168,7 @@ class WaveRedirectResourceMockEnabledTest { .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303))) .header("Location", containsString("success")); - verify(em).merge(any(Cotisation.class)); + verify(entityManager).merge(any(Cotisation.class)); } // ----------------------------------------------------------------------- @@ -350,12 +348,10 @@ class WaveRedirectResourceMockEnabledTest { cotisation.setMontantDu(new BigDecimal("7500")); cotisation.setStatut("EN_ATTENTE"); - EntityManager em = mock(EntityManager.class); when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention); + when(entityManager.find(eq(Cotisation.class), eq(cotisationId))).thenReturn(cotisation); + when(entityManager.merge(any(Cotisation.class))).thenReturn(cotisation); doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class)); - when(intentionPaiementRepository.getEntityManager()).thenReturn(em); - when(em.find(eq(Cotisation.class), eq(cotisationId))).thenReturn(cotisation); - when(em.merge(any(Cotisation.class))).thenReturn(cotisation); given() .redirects().follow(false) @@ -366,7 +362,7 @@ class WaveRedirectResourceMockEnabledTest { .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303))) .header("Location", containsString("success")); - verify(em).merge(any(Cotisation.class)); + verify(entityManager).merge(any(Cotisation.class)); } // ----------------------------------------------------------------------- diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 806057d..f6d79ab 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -10,6 +10,11 @@ quarkus.log.category."dev.lions.unionflow.server.service.MembreImportExportServi wave.api.key=test-key wave.api.secret=test-secret +# Pointe sur le Keycloak local déjà démarré → désactive OIDC DevServices (Testcontainers) +# Sans cette propriété Quarkus essaie de pull l'image Keycloak à chaque build, ce qui bloque. +quarkus.oidc.auth-server-url=http://localhost:8180/realms/unionflow +quarkus.oidc.client-id=unionflow-server + # Configuration OIDC client "admin-service" factice pour les tests # Nécessaire pour que AdminUserServiceClient (@OidcClientFilter("admin-service")) puisse être mocké par @InjectMock quarkus.oidc-client.admin-service.auth-server-url=http://localhost:8180/realms/unionflow