Découverts en cascade après désactivation du KC DevService (commits précédents).
Avant, le KC DevService masquait ces 3 bugs en fournissant auto auth-server-url
et en différant l'init Hibernate.
1. PispiPaymentProvider.institutionBic — bug SmallRye Config 3.20+ :
defaultValue = "" déclenche 'Failed to load config value' au boot.
Pattern documenté dans memory feedback_quarkus_smallrye_config_empty_default.md.
Fix : remplacer par Optional<String> + orElse("") en @PostConstruct, comme
les deux autres @ConfigProperty de la même classe.
2. quarkus.oidc.auth-server-url manquant en test :
MembreKeycloakSyncService injecte la prop sans defaultValue ⇒ requise au boot.
Auparavant fournie par KC DevService. Ajout d'un stub
http://localhost:0/realms/unionflow-test-stub dans application-test.properties
(jamais utilisé : tenant-enabled=false).
3. quarkus.hibernate-orm.mapping.format.global=ignore manquant :
Bug Quarkus 3.27 (memory feedback_quarkus_327_format_mapper.md) : avec
write-dates-as-timestamps=false + colonnes JSONB, Hibernate refuse de réutiliser
le FormatMapper REST. Opt-in pour le comportement Quarkus 4 par défaut.
Smoke test : AuthCallbackResourceTest 10/10 verts en 9.6s.
Avec quarkus.devservices.enabled=true (requis pour le PG container du test RLS),
le BuildStep inconditionnel de quarkus-keycloak-admin-client produit un
KeycloakDevServicesRequiredBuildItem → un container KC démarre inutilement
(~40s de boot ignoré, le test RLS ne touche pas à l'auth).
Override explicite dans IntegrationTestProfile.getConfigOverrides() :
- quarkus.keycloak.devservices.enabled=false → KeycloakDevServicesProcessor
skip-and-log au lieu de démarrer le container (la global enabled reste true
pour le PG container nécessaire au test).
- quarkus.oidc.tenant-enabled=false → cohérence avec application-test.properties.
Le revert précédent (commit 5cc3806) basé sur 'ne pas appauvrir' était factuellement
incorrect pour ce projet. Analyse bytecode des extensions Quarkus 3.27.3 :
- quarkus-keycloak-admin-rest-client-deployment.KeycloakDevServiceRequiredBuildStep
est annoté @BuildSteps(onlyIf = {IsDevServicesSupportedByLaunchMode,
DevServicesConfig.Enabled, KeycloakAdminClientInjectionEnabled}) et produit
inconditionnellement un KeycloakDevServicesRequiredBuildItem dès que cette
extension est dans le classpath — ignorant quarkus.oidc.tenant-enabled et
quarkus.keycloak.devservices.enabled.
- Le seul kill switch respecté est DevServicesConfig.Enabled ⇒
quarkus.devservices.enabled.
- Empiriquement : sans le global, un container quay.io/keycloak/keycloak:26.3.4
démarre à chaque test (+50s, ignore KC local 8180).
Cohérence d'archi (pas un appauvrissement) :
- H2 in-memory configuré explicitement dans le profil test.
- OIDC tenant-enabled=false (pas d'auth en test).
- Aucune autre extension utilisant DevServices dans ce profil.
Aussi : application-integration-test.properties — corrige policy-enforcer.enable
(déprécié) en policy-enforcer.enabled (kill le warning au build).
Tests : 28 tests verts en <2s, 0 container démarré.
Suite à observation que le précédent fix ciblé `quarkus.keycloak.devservices.enabled=false`
n'empêchait pas le démarrage du container KC (probablement timing build-time vs runtime).
Mode test :
- DataSource : H2 in-memory (lignes 5-8 application-test.properties — pas de Postgres needed)
- OIDC : tenant-enabled=false (pas de KC needed)
Conclusion : aucun DevService nécessaire en test → désactivation globale via
`quarkus.devservices.enabled=false` (couvre tous les services) + garde
`quarkus.keycloak.devservices.enabled=false` en sécurité.
Bénéfice : tests démarrent en 5-10s au lieu de 1-3min (boot containers KC + ryuk).
Mode `quarkus:dev` reste full DevServices (logique, on a besoin de KC en dev).
Erreur observée :
ERROR [io.qu.de.ke.KeycloakDevServicesProcessor] Admin token can not be acquired
due to a client connection timeout
(~50s boot Docker + timeouts admin token)
Cause : @QuarkusTest charge application-test.properties qui désactive OIDC
(tenant-enabled=false), mais l'extension quarkus-keycloak-* déclenche quand même
le DevService Keycloak qui télécharge/démarre un container KC 26.3.4 et tente
d'obtenir un admin token — qui timeout.
Fix non-appauvrissant : désactiver UNIQUEMENT le DevService KC en mode test.
- Dev mode : DevServices KC reste actif (utile pour quarkus:dev)
- Test mode : OIDC déjà désactivé → KC DevServices = pure perte de temps de boot
- Postgres DevServices reste actif (Hibernate a besoin d'une DB pour les tests JPA)
Ajout :
quarkus.keycloak.devservices.enabled=false (test-only via application-test.properties)
JwtPropagationFilterTest @QuarkusTest fait @Inject JwtPropagationFilter mais la
classe n'avait aucun scope CDI → UnsatisfiedResolutionException au démarrage du test.
Le commentaire interdisait @Provider (auto-register JAX-RS global qui écraserait le
service account des AdminUserServiceClient/AdminRoleServiceClient). Mais ne disait
rien contre @ApplicationScoped : c'est un scope CDI ≠ provider JAX-RS.
Fix : ajouter @ApplicationScoped (rend découvrable par CDI mais ne provoque PAS
d'enregistrement automatique JAX-RS — l'opt-in reste via @RegisterProvider explicite).
Suppression de l'import jakarta.ws.rs.ext.Provider devenu inutile.
Commentaire de classe enrichi pour clarifier la nuance scope-CDI vs provider-JAX-RS.
3 fixes pour build dev mode propre :
1. Qute @CheckedTemplate (EmailTemplateService.Templates) cassait au démarrage
→ maven-compiler-plugin <parameters>true</parameters> (noms params en bytecode)
2. Maven warning "build.plugins.plugin.version missing"
→ ajout <version>3.13.0</version> au compiler-plugin (cohérent avec api module)
3. Quarkus runtime warning "quarkus.keycloak.policy-enforcer.enable is deprecated"
→ renommé en policy-enforcer.enabled dans application.properties + application-test.properties
(depuis Quarkus 3.x, propriété renommée pour cohérence avec autres flags enabled)
Transparency opérationnelle UnionFlow — opérations sensibles consultables en temps réel selon scope.
Repository
- findRecent(limit) : N opérations les plus récentes, toutes orgs
- findRecentByOrganisation(orgId, limit) : pour scope ORG
- findRecentByUser(userId, limit) : pour scope SELF
- Tous clamps limit à [1, 500] (sécurité DoS)
Service AuditTrailQueryService
- listerRecentes(scope, orgId, userId, limit) : dispatcher SELF/ORG/ALL avec fallback liste vide si UUID requis manquant ; insensible à la casse
Resource REST AuditTrailOperationResource
- GET /api/audit-trail/recent?scope=SELF&orgId=...&userId=...&limit=50
- Default scope=SELF, limit=50
- @RolesAllowed étendu (MEMBRE inclus pour scope SELF) — un membre peut voir SES propres opérations
Tests (7 nouveaux, 11/11 cumulé service)
- recentes_All / Null defaults to ALL / Org / OrgWithoutId / Self / SelfWithoutUser / CaseInsensitive
Suite à enrichissement OrganisationResponse api 1.0.9. Mapping Entity → Response
maintenant complet pour le front-end (Sprint 10 avait déjà le mapping
Request → Entity, manquait Entity → Response).
Bump dépendance api 1.0.8 → 1.0.9. Quarkus inchangé (3.27.3).
ACTION USER : `mvn install` côté unionflow-server-impl-quarkus pour valider.
Suite à la récupération précédente (044ca4b) qui n'avait restauré que les
fichiers SUPPRIMÉS, ce commit restaure les MODIFICATIONS d'entités/services
qui étaient nécessaires pour que les fichiers restaurés compilent.
Restaurés depuis a72ab54^ (= 31330d9 + corrections) :
- Entities : Organisation, FormuleAbonnement, AuditService, MembreOrganisation, SouscriptionOrganisation, etc.
- Services : MigrerOrganisationsVersKeycloakService, ComptabilitePdfService, KycAmlService, AuditService.logKycRisqueEleve, etc.
- Resources : PaiementUnifieResource, etc.
Backend compile désormais (BUILD SUCCESS).
Le calcul précédent `getSystemLoadAverage() / getAvailableProcessors() * 100`
utilisait :
- getSystemLoadAverage() : charge 1min du NODE Linux hôte entier (12 CPU sur prod VPS)
- getAvailableProcessors() : limite conteneur K8s (1 CPU pour le pod UnionFlow)
Résultat : load 1.5 sur le node → cpuUsage = (1.5 / 1) * 100 = 150% capé à 100%.
Déclenchement constant d'alertes "CPU 100%" (19 faux positifs / 24h sur prod
le 20-21/04/2026) dès que le node fait autre chose que dormir.
Correctif : utilise com.sun.management.OperatingSystemMXBean.getProcessCpuLoad()
qui retourne la charge CPU du process JVM courant (0.0-1.0), conscient du
conteneur. Branche -1 préservée (API non dispo sur certaines JVM).
Tests mis à jour : AlertMonitoringServiceMockStaticCoverageTest injecte
désormais un com.sun.management.OperatingSystemMXBean et mocke
getProcessCpuLoad() (compatible mock-maker-inline déjà configuré).
AlertMonitoringServiceTest conserve sa logique OS-agnostique via des
thresholds extrêmes (-1 toujours / 100 jamais).
- PUT /{id}: check appartenance ADMIN_ORGANISATION (403 si pas membre)
- GET /statistiques: stats scoped à l'org active pour ADMIN_ORGANISATION
- OrganisationService.obtenirStatistiquesParOrganisation(): stats mono-org
- MembreOrganisationRepository.findByMembreEmailAndOrganisationId()
- application-dev: IP 192.168.1.145→localhost pour Keycloak
- OrganisationService.convertToResponse: ne plus setter typeAssociation ni typeLibelle
- Utiliser uniquement typeOrganisation et typeOrganisationLibelle
- Corriger tests: assertions sur typeOrganisationLibelle au lieu de typeLibelle
- ADMIN_ENTITE→ADMIN_ORGANISATION dans Javadoc et README
- OrganisationModuleService: retirer TONTINE de MUTUELLE (BCEAO-réglementé, incompatible)
- Ajouter TONTINE aux ASSOCIATION et CLUB_SERVICE (pratique courante Afrique de l'Ouest)
- V18 migration: aligner modules_requis avec le code Java
La V18 a été modifiée (fusion MUTUELLE_EPARGNE+CREDIT → MUTUELLE).
En prod, le checksum en DB ne correspond plus → Flyway refuse de démarrer.
repair-at-start met à jour la schema_history automatiquement.
Problème : après paiement Wave, le membre restait SIMPLEMEMBER car :
1. La méthode bouclait sur TOUS les liens de l'org et promouvait le
premier non-ACTIF (pas forcément le payeur)
2. Si aucun lien n'existait, créait un lien avec SIMPLEMEMBER sans promotion
3. Early return après la première promotion → si le mauvais membre était
promu, le payeur restait bloqué
Nouvelle logique :
1. Identifier le caller (JWT email) = le membre qui a réellement payé
2. Vérifier/créer son lien MembreOrganisation (activer si en attente)
3. Promouvoir ce membre spécifique en ORGADMIN (DB)
4. Syncer Keycloak ADMIN_ORGANISATION (non-bloquant)
Résultat : après paiement, le payeur est automatiquement promu ORGADMIN
avec statut ACTIF et accède directement à son dashboard.
TypeReferenceService.creer() :
- Si code non fourni ou vide : auto-généré depuis le libellé via genererCodeDepuisLibelle()
- Si code fourni : nettoyé via normaliserCode() (strip accents, UPPER_SNAKE_CASE)
- Dédoublonnage automatique : si le code existe, suffixe _2, _3, etc.
Méthodes ajoutées :
- genererCodeDepuisLibelle(String) : libellé → UPPER_SNAKE_CASE sans accents
Ex: 'Mutuelle d''Épargne' → 'MUTUELLE_D_EPARGNE'
- normaliserCode(String) : Normalizer.NFD + strip diacritics + [^A-Za-z0-9] → _
- assurerUniciteCode(String, String, UUID) : suffixe incrémental si doublon
Les types système (est_systeme=true, seeded en V18) gardent leurs codes figés.
Seuls les types créés par l'utilisateur bénéficient de l'auto-génération.
Une mutuelle (MEC/COOPEC) fait TOUJOURS épargne ET crédit conjointement
dans le cadre réglementaire BCEAO/UEMOA. La séparation en deux types
n'avait pas de réalité terrain.
V18 corrigée :
- MUTUELLE_EPARGNE + MUTUELLE_CREDIT supprimés
- MUTUELLE ajouté : modules EPARGNE,CREDIT,FINANCE,LCB_FT (complet)
- COOPERATIVE enrichi : ajout EPARGNE + VOTES (réalité terrain — les coopératives
ont des AG avec votes et proposent souvent de l'épargne à leurs membres)
Passe de 17 → 16 types d'organisation.
Le mapping mobile _mapTypeOrganisationBilling garde les anciens codes en fallback
pour rétrocompatibilité.
- pom.xml : mise à jour dépendances
- application.properties : ajustements config
- MembreServiceTest, EntityCoverageTest : tests mis à jour pour nouveautés
- .gitignore : ajout du.exe.stackdump (dump Windows bash)
- WebhookWave : entité pour logs webhooks Wave (idempotence + audit)
- WaveRedirectResource : endpoint de retour après paiement Wave
(redirige vers l'app mobile avec le statut)
- KeycloakAdminHttpClient (nouveau) : client HTTP natif (java.net.http.HttpClient)
pour contourner les problèmes de désérialisation avec RESTEasy sur certains
endpoints Keycloak 26+ (bruteForceStrategy, cpuInfo inconnus).
Utilise ObjectMapper avec FAIL_ON_UNKNOWN_PROPERTIES=false.
- AdminUserService : utilisation correcte de AdminUserServiceClient + AdminRoleServiceClient
avec AdminServiceTokenHeadersFactory pour l'auth.
- ModuleAccessFilter : améliorations de la logique @RequiresModule.
Migrations :
- V25 : numero_transaction nullable dans paiements (legacy V1 NOT NULL bloquant INSERT)
- V26 : autres colonnes legacy NOT NULL V1 (type_paiement, statut_paiement, etc.)
rendues nullables pour alignement avec l'entité Paiement
Refactor Paiement/PaiementObjet : mise à jour entités, repository, resource, service
pour cohérence avec le nouveau module Versement. Tests associés supprimés/ajustés.