Files
unionflow-server-impl-quarkus/src/test/java/dev/lions/unionflow/server/service/MembreKeycloakSyncServiceTest.java
2026-03-28 16:51:14 +00:00

736 lines
31 KiB
Java

package dev.lions.unionflow.server.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import dev.lions.unionflow.server.client.UserServiceClient;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.user.manager.dto.user.UserDTO;
import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO;
import dev.lions.user.manager.dto.user.UserSearchResultDTO;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import java.util.Collections;
import java.util.Optional;
import java.util.UUID;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@QuarkusTest
class MembreKeycloakSyncServiceTest {
@Inject
MembreKeycloakSyncService syncService;
@InjectMock
MembreRepository membreRepository;
@InjectMock
@RestClient
UserServiceClient userServiceClient;
// =========================================================================
// provisionKeycloakUser
// =========================================================================
@Test
@DisplayName("provisionKeycloakUser échoue si le membre n'existe pas")
void provisionKeycloakUser_failsIfMembreNotFound() {
UUID membreId = UUID.randomUUID();
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.empty());
assertThatThrownBy(() -> syncService.provisionKeycloakUser(membreId))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("provisionKeycloakUser échoue si le membre a déjà un ID Keycloak")
void provisionKeycloakUser_failsIfAlreadyLinked() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setKeycloakId(UUID.randomUUID());
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
assertThatThrownBy(() -> syncService.provisionKeycloakUser(membreId))
.isInstanceOf(IllegalStateException.class);
}
@Test
@DisplayName("provisionKeycloakUser crée un utilisateur Keycloak et lie le membre")
void provisionKeycloakUser_createsAndLinks() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setEmail("test@unionflow.dev");
membre.setNom("Doe");
membre.setPrenom("John");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserSearchResultDTO searchResult = new UserSearchResultDTO();
searchResult.setUsers(Collections.emptyList());
when(userServiceClient.searchUsers(any(UserSearchCriteriaDTO.class))).thenReturn(searchResult);
UserDTO createdUser = new UserDTO();
createdUser.setId(UUID.randomUUID().toString());
when(userServiceClient.createUser(any(UserDTO.class), anyString())).thenReturn(createdUser);
syncService.provisionKeycloakUser(membreId);
verify(userServiceClient).createUser(any(UserDTO.class), eq("unionflow"));
verify(membreRepository).persist(membre);
verify(userServiceClient).sendVerificationEmail(eq(createdUser.getId()), eq("unionflow"));
}
@Test
@DisplayName("provisionKeycloakUser lève IllegalStateException si un user Keycloak existe déjà avec cet email")
void provisionKeycloakUser_failsIfKeycloakUserAlreadyExists() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setEmail("duplicate@unionflow.dev");
membre.setNom("Dupont");
membre.setPrenom("Pierre");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserDTO existingUser = new UserDTO();
existingUser.setId(UUID.randomUUID().toString());
existingUser.setEmail("duplicate@unionflow.dev");
UserSearchResultDTO searchResult = new UserSearchResultDTO();
searchResult.setUsers(Collections.singletonList(existingUser));
when(userServiceClient.searchUsers(any(UserSearchCriteriaDTO.class))).thenReturn(searchResult);
assertThatThrownBy(() -> syncService.provisionKeycloakUser(membreId))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("duplicate@unionflow.dev");
}
@Test
@DisplayName("provisionKeycloakUser continue si la recherche Keycloak lève une exception non-ISE")
void provisionKeycloakUser_continuesOnSearchException() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setEmail("search-fail@unionflow.dev");
membre.setNom("Search");
membre.setPrenom("Fail");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
// search throws a non-ISE exception
when(userServiceClient.searchUsers(any())).thenThrow(new RuntimeException("Service unavailable"));
UserDTO createdUser = new UserDTO();
createdUser.setId(UUID.randomUUID().toString());
when(userServiceClient.createUser(any(UserDTO.class), anyString())).thenReturn(createdUser);
// Should not throw — it logs warning and continues
syncService.provisionKeycloakUser(membreId);
verify(userServiceClient).createUser(any(UserDTO.class), eq("unionflow"));
}
@Test
@DisplayName("provisionKeycloakUser tolère un ID Keycloak invalide (non-UUID) dans la réponse")
void provisionKeycloakUser_toleratesInvalidKeycloakId() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setEmail("invalid-id@unionflow.dev");
membre.setNom("Invalid");
membre.setPrenom("Id");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserSearchResultDTO searchResult = new UserSearchResultDTO();
searchResult.setUsers(Collections.emptyList());
when(userServiceClient.searchUsers(any())).thenReturn(searchResult);
UserDTO createdUser = new UserDTO();
createdUser.setId("not-a-valid-uuid!!!"); // invalid UUID
when(userServiceClient.createUser(any(UserDTO.class), anyString())).thenReturn(createdUser);
// Should not throw — logs warning and persists with null keycloakId
syncService.provisionKeycloakUser(membreId);
verify(membreRepository).persist(membre);
}
@Test
@DisplayName("provisionKeycloakUser lève RuntimeException si la création Keycloak échoue")
void provisionKeycloakUser_throwsRuntimeExceptionOnCreateFailure() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setEmail("create-fail@unionflow.dev");
membre.setNom("Create");
membre.setPrenom("Fail");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserSearchResultDTO searchResult = new UserSearchResultDTO();
searchResult.setUsers(Collections.emptyList());
when(userServiceClient.searchUsers(any())).thenReturn(searchResult);
when(userServiceClient.createUser(any(UserDTO.class), anyString()))
.thenThrow(new RuntimeException("Keycloak create failed"));
assertThatThrownBy(() -> syncService.provisionKeycloakUser(membreId))
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("Impossible de créer le compte Keycloak");
}
@Test
@DisplayName("provisionKeycloakUser tolère l'échec d'envoi de l'email de vérification")
void provisionKeycloakUser_toleratesVerificationEmailFailure() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setEmail("email-fail@unionflow.dev");
membre.setNom("Email");
membre.setPrenom("Fail");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserSearchResultDTO searchResult = new UserSearchResultDTO();
searchResult.setUsers(Collections.emptyList());
when(userServiceClient.searchUsers(any())).thenReturn(searchResult);
UserDTO createdUser = new UserDTO();
createdUser.setId(UUID.randomUUID().toString());
when(userServiceClient.createUser(any(UserDTO.class), anyString())).thenReturn(createdUser);
doThrow(new RuntimeException("SMTP unavailable"))
.when(userServiceClient).sendVerificationEmail(anyString(), anyString());
// Should not throw — email failure is non-blocking
syncService.provisionKeycloakUser(membreId);
verify(membreRepository).persist(membre);
}
// =========================================================================
// promouvoirAdminOrganisationDansKeycloak
// =========================================================================
@Test
@DisplayName("promouvoirAdminOrganisation échoue si le membre n'existe pas")
void promouvoirAdminOrganisation_failsIfMembreNotFound() {
UUID membreId = UUID.randomUUID();
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.empty());
assertThatThrownBy(() -> syncService.promouvoirAdminOrganisationDansKeycloak(membreId))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("promouvoirAdminOrganisation assigne ADMIN_ORGANISATION et retire MEMBRE/MEMBRE_ACTIF")
void promouvoirAdminOrganisation_assignsAdminRoleAndRemovesMemberRoles() {
UUID membreId = UUID.randomUUID();
UUID keycloakId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setKeycloakId(keycloakId);
membre.setEmail("admin@unionflow.dev");
membre.setNom("Admin");
membre.setPrenom("New");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserDTO user = new UserDTO();
user.setId(keycloakId.toString());
user.setEnabled(true);
user.setRealmRoles(new java.util.ArrayList<>(java.util.List.of("MEMBRE", "MEMBRE_ACTIF")));
user.setRealmName("unionflow");
when(userServiceClient.getUserById(eq(keycloakId.toString()), eq("unionflow"))).thenReturn(user);
when(userServiceClient.updateUser(anyString(), any(UserDTO.class), anyString())).thenReturn(user);
syncService.promouvoirAdminOrganisationDansKeycloak(membreId);
verify(userServiceClient).updateUser(eq(keycloakId.toString()), any(UserDTO.class), eq("unionflow"));
assertThat(user.getRealmRoles()).contains("ADMIN_ORGANISATION");
assertThat(user.getRealmRoles()).doesNotContain("MEMBRE", "MEMBRE_ACTIF");
}
@Test
@DisplayName("promouvoirAdminOrganisation active le compte s'il est désactivé dans Keycloak")
void promouvoirAdminOrganisation_enablesDisabledAccount() {
UUID membreId = UUID.randomUUID();
UUID keycloakId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setKeycloakId(keycloakId);
membre.setEmail("disabled-admin@unionflow.dev");
membre.setNom("Disabled");
membre.setPrenom("Admin");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserDTO user = new UserDTO();
user.setId(keycloakId.toString());
user.setEnabled(false); // désactivé
user.setRealmRoles(new java.util.ArrayList<>());
user.setRealmName("unionflow");
when(userServiceClient.getUserById(anyString(), anyString())).thenReturn(user);
when(userServiceClient.updateUser(anyString(), any(UserDTO.class), anyString())).thenReturn(user);
syncService.promouvoirAdminOrganisationDansKeycloak(membreId);
assertThat(user.getEnabled()).isTrue();
verify(userServiceClient).updateUser(anyString(), any(UserDTO.class), anyString());
}
@Test
@DisplayName("promouvoirAdminOrganisation provisionne Keycloak si keycloakId est null")
void promouvoirAdminOrganisation_provisionesIfNoKeycloakAccount() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setEmail("no-kc-admin@unionflow.dev");
membre.setNom("No");
membre.setPrenom("KC");
// keycloakId == null
UUID newKeycloakId = UUID.randomUUID();
Membre membreWithKc = new Membre();
membreWithKc.setId(membreId);
membreWithKc.setEmail("no-kc-admin@unionflow.dev");
membreWithKc.setNom("No");
membreWithKc.setPrenom("KC");
membreWithKc.setKeycloakId(newKeycloakId);
when(membreRepository.findByIdOptional(membreId))
.thenReturn(Optional.of(membre)) // 1er appel (promouvoir) : pas de keycloakId → déclenche provisionnement
.thenReturn(Optional.of(membre)) // 2e appel (provisionKeycloakUser interne) : pas de keycloakId
.thenReturn(Optional.of(membreWithKc)); // 3e appel : rechargement après provisionnement
UserSearchResultDTO searchResult = new UserSearchResultDTO();
searchResult.setUsers(java.util.Collections.emptyList());
when(userServiceClient.searchUsers(any())).thenReturn(searchResult);
UserDTO createdUser = new UserDTO();
createdUser.setId(newKeycloakId.toString());
when(userServiceClient.createUser(any(UserDTO.class), anyString())).thenReturn(createdUser);
UserDTO fetchedUser = new UserDTO();
fetchedUser.setId(newKeycloakId.toString());
fetchedUser.setEnabled(true);
fetchedUser.setRealmRoles(new java.util.ArrayList<>());
fetchedUser.setRealmName("unionflow");
when(userServiceClient.getUserById(eq(newKeycloakId.toString()), anyString())).thenReturn(fetchedUser);
when(userServiceClient.updateUser(anyString(), any(UserDTO.class), anyString())).thenReturn(fetchedUser);
syncService.promouvoirAdminOrganisationDansKeycloak(membreId);
verify(userServiceClient).createUser(any(UserDTO.class), eq("unionflow"));
verify(userServiceClient).updateUser(eq(newKeycloakId.toString()), any(UserDTO.class), eq("unionflow"));
}
@Test
@DisplayName("promouvoirAdminOrganisation lève RuntimeException si l'appel Keycloak échoue")
void promouvoirAdminOrganisation_throwsRuntimeExceptionOnKeycloakFailure() {
UUID membreId = UUID.randomUUID();
UUID keycloakId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setKeycloakId(keycloakId);
membre.setEmail("fail-admin@unionflow.dev");
membre.setNom("Fail");
membre.setPrenom("Admin");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
when(userServiceClient.getUserById(anyString(), anyString()))
.thenThrow(new RuntimeException("Keycloak unreachable"));
assertThatThrownBy(() -> syncService.promouvoirAdminOrganisationDansKeycloak(membreId))
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("Impossible de promouvoir le compte Keycloak");
}
// =========================================================================
// syncMembreToKeycloak
// =========================================================================
@Test
@DisplayName("syncMembreToKeycloak lève NotFoundException si le membre n'existe pas")
void syncMembreToKeycloak_failsIfMembreNotFound() {
UUID membreId = UUID.randomUUID();
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.empty());
assertThatThrownBy(() -> syncService.syncMembreToKeycloak(membreId))
.isInstanceOf(NotFoundException.class);
}
@Test
@DisplayName("syncMembreToKeycloak provisionne automatiquement si pas de compte Keycloak")
void syncMembreToKeycloak_provisionesIfNoKeycloakAccount() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setEmail("no-kc@unionflow.dev");
membre.setNom("No");
membre.setPrenom("KC");
// keycloakId == null → doit appeler provisionKeycloakUser
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserSearchResultDTO searchResult = new UserSearchResultDTO();
searchResult.setUsers(Collections.emptyList());
when(userServiceClient.searchUsers(any())).thenReturn(searchResult);
UserDTO createdUser = new UserDTO();
createdUser.setId(UUID.randomUUID().toString());
when(userServiceClient.createUser(any(UserDTO.class), anyString())).thenReturn(createdUser);
syncService.syncMembreToKeycloak(membreId);
// provisionKeycloakUser was invoked internally
verify(userServiceClient).createUser(any(UserDTO.class), eq("unionflow"));
}
@Test
@DisplayName("syncMembreToKeycloak met à jour le user Keycloak si le membre est déjà lié")
void syncMembreToKeycloak_updatesExistingKeycloakUser() {
UUID membreId = UUID.randomUUID();
UUID keycloakId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setKeycloakId(keycloakId);
membre.setEmail("sync@unionflow.dev");
membre.setNom("Sync");
membre.setPrenom("Test");
membre.setActif(true);
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserDTO remoteUser = new UserDTO();
remoteUser.setId(keycloakId.toString());
remoteUser.setRealmName("unionflow");
when(userServiceClient.getUserById(eq(keycloakId.toString()), eq("unionflow")))
.thenReturn(remoteUser);
syncService.syncMembreToKeycloak(membreId);
verify(userServiceClient).updateUser(eq(keycloakId.toString()), any(UserDTO.class), eq("unionflow"));
}
@Test
@DisplayName("syncMembreToKeycloak lève RuntimeException si l'appel getUserById échoue")
void syncMembreToKeycloak_throwsRuntimeExceptionOnGetUserFailure() {
UUID membreId = UUID.randomUUID();
UUID keycloakId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setKeycloakId(keycloakId);
membre.setEmail("get-fail@unionflow.dev");
membre.setNom("Get");
membre.setPrenom("Fail");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
when(userServiceClient.getUserById(anyString(), anyString()))
.thenThrow(new RuntimeException("Keycloak unreachable"));
assertThatThrownBy(() -> syncService.syncMembreToKeycloak(membreId))
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("Impossible de synchroniser le user Keycloak");
}
@Test
@DisplayName("syncMembreToKeycloak utilise actif=true par défaut si membre.actif est null")
void syncMembreToKeycloak_usesDefaultEnabledWhenActifIsNull() {
UUID membreId = UUID.randomUUID();
UUID keycloakId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setKeycloakId(keycloakId);
membre.setEmail("null-actif@unionflow.dev");
membre.setNom("Null");
membre.setPrenom("Actif");
membre.setActif(null); // null → defaults to true
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
UserDTO remoteUser = new UserDTO();
remoteUser.setId(keycloakId.toString());
remoteUser.setRealmName("unionflow");
when(userServiceClient.getUserById(anyString(), anyString())).thenReturn(remoteUser);
syncService.syncMembreToKeycloak(membreId);
verify(userServiceClient).updateUser(anyString(), any(UserDTO.class), anyString());
}
// =========================================================================
// syncKeycloakToMembre
// =========================================================================
@Test
@DisplayName("syncKeycloakToMembre ne fait rien si aucun Membre n'est lié au user Keycloak")
void syncKeycloakToMembre_doesNothingIfNoMembreMapped() {
String keycloakUserId = UUID.randomUUID().toString();
when(membreRepository.findByKeycloakUserId(keycloakUserId)).thenReturn(Optional.empty());
syncService.syncKeycloakToMembre(keycloakUserId, "unionflow");
verifyNoInteractions(userServiceClient);
}
@Test
@DisplayName("syncKeycloakToMembre met à jour le Membre avec les données Keycloak")
void syncKeycloakToMembre_updatesMembreFromKeycloak() {
String keycloakUserId = UUID.randomUUID().toString();
Membre membre = new Membre();
membre.setId(UUID.randomUUID());
membre.setEmail("old@unionflow.dev");
membre.setNom("OldNom");
membre.setPrenom("OldPrenom");
when(membreRepository.findByKeycloakUserId(keycloakUserId)).thenReturn(Optional.of(membre));
UserDTO keycloakUser = new UserDTO();
keycloakUser.setId(keycloakUserId);
keycloakUser.setPrenom("NewPrenom");
keycloakUser.setNom("NewNom");
keycloakUser.setEmail("new@unionflow.dev");
keycloakUser.setEnabled(false);
when(userServiceClient.getUserById(eq(keycloakUserId), eq("unionflow"))).thenReturn(keycloakUser);
syncService.syncKeycloakToMembre(keycloakUserId, "unionflow");
assertThat(membre.getPrenom()).isEqualTo("NewPrenom");
assertThat(membre.getNom()).isEqualTo("NewNom");
assertThat(membre.getEmail()).isEqualTo("new@unionflow.dev");
assertThat(membre.getActif()).isFalse();
verify(membreRepository).persist(membre);
}
@Test
@DisplayName("syncKeycloakToMembre utilise DEFAULT_REALM si realm est null")
void syncKeycloakToMembre_usesDefaultRealmWhenNull() {
String keycloakUserId = UUID.randomUUID().toString();
Membre membre = new Membre();
membre.setId(UUID.randomUUID());
when(membreRepository.findByKeycloakUserId(keycloakUserId)).thenReturn(Optional.of(membre));
UserDTO keycloakUser = new UserDTO();
keycloakUser.setId(keycloakUserId);
keycloakUser.setPrenom("P");
keycloakUser.setNom("N");
keycloakUser.setEmail("e@e.com");
keycloakUser.setEnabled(true);
when(userServiceClient.getUserById(eq(keycloakUserId), eq("unionflow"))).thenReturn(keycloakUser);
syncService.syncKeycloakToMembre(keycloakUserId, null); // null realm
verify(userServiceClient).getUserById(eq(keycloakUserId), eq("unionflow"));
}
@Test
@DisplayName("syncKeycloakToMembre lève RuntimeException si getUserById échoue")
void syncKeycloakToMembre_throwsRuntimeExceptionOnFailure() {
String keycloakUserId = UUID.randomUUID().toString();
Membre membre = new Membre();
membre.setId(UUID.randomUUID());
when(membreRepository.findByKeycloakUserId(keycloakUserId)).thenReturn(Optional.of(membre));
when(userServiceClient.getUserById(anyString(), anyString()))
.thenThrow(new RuntimeException("Timeout"));
assertThatThrownBy(() -> syncService.syncKeycloakToMembre(keycloakUserId, "unionflow"))
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("Impossible de synchroniser depuis Keycloak");
}
// =========================================================================
// findMembreByKeycloakUserId
// =========================================================================
@Test
@DisplayName("findMembreByKeycloakUserId retourne Optional.empty() si aucun membre n'est lié")
void findMembreByKeycloakUserId_returnsEmptyIfNotFound() {
String keycloakUserId = UUID.randomUUID().toString();
when(membreRepository.findByKeycloakUserId(keycloakUserId)).thenReturn(Optional.empty());
Optional<Membre> result = syncService.findMembreByKeycloakUserId(keycloakUserId);
assertThat(result).isEmpty();
}
@Test
@DisplayName("findMembreByKeycloakUserId retourne le Membre lié si trouvé")
void findMembreByKeycloakUserId_returnsMembre() {
String keycloakUserId = UUID.randomUUID().toString();
Membre membre = new Membre();
membre.setId(UUID.randomUUID());
membre.setEmail("linked@unionflow.dev");
when(membreRepository.findByKeycloakUserId(keycloakUserId)).thenReturn(Optional.of(membre));
Optional<Membre> result = syncService.findMembreByKeycloakUserId(keycloakUserId);
assertThat(result).isPresent();
assertThat(result.get()).isSameAs(membre);
}
// =========================================================================
// unlinkKeycloakUser
// =========================================================================
@Test
@DisplayName("unlinkKeycloakUser lève NotFoundException si le membre n'existe pas")
void unlinkKeycloakUser_failsIfMembreNotFound() {
UUID membreId = UUID.randomUUID();
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.empty());
assertThatThrownBy(() -> syncService.unlinkKeycloakUser(membreId))
.isInstanceOf(NotFoundException.class);
}
// =========================================================================
// provisionKeycloakUser — branche searchResult == null (L97 branche null)
// =========================================================================
@Test
@DisplayName("provisionKeycloakUser avec searchResult==null depuis API → continue (branche null L97)")
void provisionKeycloakUser_searchResultNull_continues() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setNom("Null");
membre.setPrenom("Search");
membre.setEmail("null-search-" + membreId + "@test.com");
membre.setKeycloakId(null);
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
// searchResult == null → condition L97 = false → continue
when(userServiceClient.searchUsers(any(UserSearchCriteriaDTO.class))).thenReturn(null);
// provisionKeycloakUser tente ensuite de créer l'utilisateur via createUser
// On mock createUser pour qu'il retourne un UserDTO valide
UserDTO createdUser = new UserDTO();
createdUser.setId(UUID.randomUUID().toString());
createdUser.setUsername(membre.getEmail());
when(userServiceClient.createUser(any(UserDTO.class), anyString())).thenReturn(createdUser);
// Ne doit pas lever d'exception (searchResult null → condition false → continue)
org.assertj.core.api.Assertions.assertThatCode(() -> syncService.provisionKeycloakUser(membreId))
.doesNotThrowAnyException();
}
@Test
@DisplayName("provisionKeycloakUser avec searchResult.getUsers()==null → continue (branche getUsers()==null L97)")
void provisionKeycloakUser_usersNull_continues() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setNom("UsersNull");
membre.setPrenom("Test");
membre.setEmail("users-null-" + membreId + "@test.com");
membre.setKeycloakId(null);
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
// searchResult non-null mais users == null → condition L97 = false → continue
UserSearchResultDTO searchResult = new UserSearchResultDTO();
searchResult.setUsers(null);
when(userServiceClient.searchUsers(any(UserSearchCriteriaDTO.class))).thenReturn(searchResult);
UserDTO createdUser = new UserDTO();
createdUser.setId(UUID.randomUUID().toString());
createdUser.setUsername(membre.getEmail());
when(userServiceClient.createUser(any(UserDTO.class), anyString())).thenReturn(createdUser);
org.assertj.core.api.Assertions.assertThatCode(() -> syncService.provisionKeycloakUser(membreId))
.doesNotThrowAnyException();
}
// =========================================================================
// L117: createdUser.getId() == null → false branch (skip setKeycloakId)
// =========================================================================
@Test
@DisplayName("provisionKeycloakUser avec createdUser.getId() null → L117 false → keycloakId non settée")
void provisionKeycloakUser_createdUserIdNull_L117False_keycloakIdNotSet() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setNom("IdNull");
membre.setPrenom("Test");
membre.setEmail("id-null-" + membreId + "@test.com");
membre.setKeycloakId(null);
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
// searchResult null ou sans users → membre not found → create
UserSearchResultDTO searchResult = new UserSearchResultDTO();
searchResult.setUsers(null);
when(userServiceClient.searchUsers(any(UserSearchCriteriaDTO.class))).thenReturn(searchResult);
// createdUser with getId() = null → L117: null != null = false → skip setKeycloakId
UserDTO createdUser = new UserDTO();
createdUser.setId(null); // null → L117 false branch
when(userServiceClient.createUser(any(UserDTO.class), anyString())).thenReturn(createdUser);
org.assertj.core.api.Assertions.assertThatCode(() -> syncService.provisionKeycloakUser(membreId))
.doesNotThrowAnyException();
// keycloakId should remain null since getId() was null
assertThat(membre.getKeycloakId()).isNull();
}
@Test
@DisplayName("unlinkKeycloakUser ne fait rien si le membre n'a pas de compte Keycloak lié")
void unlinkKeycloakUser_doesNothingIfNoKeycloakId() {
UUID membreId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setKeycloakId(null);
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
syncService.unlinkKeycloakUser(membreId);
// persist should not be called when there is nothing to unlink
verify(membreRepository, never()).persist(any(Membre.class));
}
@Test
@DisplayName("unlinkKeycloakUser supprime le lien Keycloak et persiste le membre")
void unlinkKeycloakUser_removesKeycloakLink() {
UUID membreId = UUID.randomUUID();
UUID keycloakId = UUID.randomUUID();
Membre membre = new Membre();
membre.setId(membreId);
membre.setKeycloakId(keycloakId);
membre.setNom("Unlink");
membre.setPrenom("Test");
when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
syncService.unlinkKeycloakUser(membreId);
assertThat(membre.getKeycloakId()).isNull();
verify(membreRepository).persist(membre);
}
}