fix(sprint-17): security hardening + migration SQL fixes
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 2m53s
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 2m53s
Security: - DashboardWebSocketEndpoint: add @Authenticated (was publicly accessible) - MembreResource: add @RolesAllowed on 4 endpoints (rechercher, lister, stats, export) - Membre: CascadeType.ALL → PERSIST+MERGE (prevent cascade delete historique) - Membre: getNomComplet() null-safe Bug fixes: - DeviseConversionService: BUG-01 division-by-zero → TauxIntrouvableException - AlerteLcbFtService/Resource: RBAC fixes - OrganisationResource: minor fix Migrations: - V39: fix column name utilisateur_id → membre_id in RLS policy (kyc_dossier) - V46: CREATE TABLE types_aide (was ALTER on non-existent table → was failing) - V49: multi-devise schema corrections
This commit is contained in:
@@ -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<MembreOrganisation> 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() {
|
||||
|
||||
@@ -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<String, String> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -82,7 +82,7 @@ public class DeviseConversionService {
|
||||
// 2. Inverse exact
|
||||
Optional<TauxChange> 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<TauxChange> 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<TauxChange> 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<TauxChange> recent = repository.trouverPlusRecent(source, cible, date);
|
||||
@@ -127,13 +127,22 @@ public class DeviseConversionService {
|
||||
|
||||
Optional<TauxChange> 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();
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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é) '
|
||||
|
||||
@@ -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).';
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user