736 lines
31 KiB
Java
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);
|
|
}
|
|
}
|