diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 2d674428..8a866a32 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -197,12 +197,12 @@ "consortia.tenants.item.post" ], "modulePermissions": [ - "consortia.consortia-configuration.item.post", "perms.users.item.put", "perms.users.item.post", "perms.users.assign.immutable", "perms.users.assign.mutable", - "users.collection.get" + "users.collection.get", + "user-tenants.collection.get" ] }, { diff --git a/src/main/java/org/folio/consortia/client/ConsortiaConfigurationClient.java b/src/main/java/org/folio/consortia/client/ConsortiaConfigurationClient.java deleted file mode 100644 index b6511c18..00000000 --- a/src/main/java/org/folio/consortia/client/ConsortiaConfigurationClient.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.folio.consortia.client; - -import org.folio.consortia.domain.dto.ConsortiaConfiguration; -import org.folio.spring.config.FeignClientConfiguration; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; - -@FeignClient(name = "consortia-configuration" , configuration = FeignClientConfiguration.class) -public interface ConsortiaConfigurationClient { - - @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - void saveConfiguration(@RequestBody ConsortiaConfiguration configuration); - -} diff --git a/src/main/java/org/folio/consortia/client/SyncPrimaryAffiliationClient.java b/src/main/java/org/folio/consortia/client/SyncPrimaryAffiliationClient.java deleted file mode 100644 index bb042004..00000000 --- a/src/main/java/org/folio/consortia/client/SyncPrimaryAffiliationClient.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.folio.consortia.client; - -import org.folio.consortia.domain.dto.SyncPrimaryAffiliationBody; -import org.folio.spring.config.FeignClientConfiguration; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; - -@FeignClient(name = "consortia", configuration = FeignClientConfiguration.class) -public interface SyncPrimaryAffiliationClient { - @PostMapping(value = "/{consortiumId}/tenants/{tenantId}/sync-primary-affiliations") - void syncPrimaryAffiliations(@PathVariable String consortiumId, @PathVariable String tenantId, @RequestParam String centralTenantId); - - @PostMapping(value = "/{consortiumId}/tenants/{tenantId}/create-primary-affiliations", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - SyncPrimaryAffiliationBody savePrimaryAffiliations(@RequestBody SyncPrimaryAffiliationBody syncPrimaryAffiliationBody, - @PathVariable String consortiumId, @PathVariable String tenantId, @RequestParam String centralTenantId); - -} diff --git a/src/main/java/org/folio/consortia/client/UserTenantsClient.java b/src/main/java/org/folio/consortia/client/UserTenantsClient.java index badc7665..d0a0d4c7 100644 --- a/src/main/java/org/folio/consortia/client/UserTenantsClient.java +++ b/src/main/java/org/folio/consortia/client/UserTenantsClient.java @@ -1,16 +1,21 @@ package org.folio.consortia.client; import org.folio.consortia.domain.dto.UserTenant; +import org.folio.consortia.domain.dto.UserTenantCollection; import org.folio.spring.config.FeignClientConfiguration; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @FeignClient(name = "user-tenants" , configuration = FeignClientConfiguration.class) public interface UserTenantsClient { + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + UserTenantCollection getUserTenants(); + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) void postUserTenant(@RequestBody UserTenant userTenant); diff --git a/src/main/java/org/folio/consortia/controller/TenantController.java b/src/main/java/org/folio/consortia/controller/TenantController.java index 708f0b83..79abe41c 100644 --- a/src/main/java/org/folio/consortia/controller/TenantController.java +++ b/src/main/java/org/folio/consortia/controller/TenantController.java @@ -1,7 +1,5 @@ package org.folio.consortia.controller; -import static org.folio.spring.scope.FolioExecutionScopeExecutionContextManager.getRunnableWithCurrentFolioContext; -import static org.folio.spring.scope.FolioExecutionScopeExecutionContextManager.getRunnableWithFolioContext; import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.NO_CONTENT; @@ -14,11 +12,9 @@ import org.folio.consortia.domain.dto.TenantDetails.SetupStatusEnum; import org.folio.consortia.rest.resource.TenantsApi; import org.folio.consortia.service.SyncPrimaryAffiliationService; +import org.folio.consortia.service.TenantManager; import org.folio.consortia.service.TenantService; -import org.folio.consortia.utils.TenantContextUtils; -import org.folio.spring.FolioExecutionContext; import org.jetbrains.annotations.NotNull; -import org.springframework.core.task.TaskExecutor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -34,45 +30,44 @@ @RequiredArgsConstructor public class TenantController implements TenantsApi { - private final TenantService service; - private final TaskExecutor asyncTaskExecutor; + private final TenantService tenantService; + private final TenantManager tenantManager; private final SyncPrimaryAffiliationService syncPrimaryAffiliationService; - private final FolioExecutionContext folioExecutionContext; + @Override public ResponseEntity getTenants(UUID consortiumId, Integer offset, Integer limit) { - return ResponseEntity.ok(service.get(consortiumId, offset, limit)); + return ResponseEntity.ok(tenantManager.get(consortiumId, offset, limit)); } @Override public ResponseEntity saveTenant(UUID consortiumId, @Validated Tenant tenant, UUID adminUserId) { - return ResponseEntity.status(CREATED).body(service.save(consortiumId, adminUserId, tenant)); + return ResponseEntity.status(CREATED).body(tenantManager.save(consortiumId, adminUserId, tenant)); } @Override public ResponseEntity updateTenant(UUID consortiumId, String tenantId, @Validated Tenant tenant) { - return ResponseEntity.ok(service.update(consortiumId, tenantId, tenant)); + return ResponseEntity.ok(tenantManager.update(consortiumId, tenantId, tenant)); } @Override public ResponseEntity deleteTenantById(UUID consortiumId, String tenantId) { - service.delete(consortiumId, tenantId); + tenantManager.delete(consortiumId, tenantId); return ResponseEntity.status(NO_CONTENT).build(); } @Override public ResponseEntity getTenantDetailsById(UUID consortiumId, String tenantId) { - return ResponseEntity.ok(service.getTenantDetailsById(consortiumId, tenantId)); + return ResponseEntity.ok(tenantService.getTenantDetailsById(consortiumId, tenantId)); } @Override public ResponseEntity syncPrimaryAffiliations(UUID consortiumId, String tenantId, @NotNull String centralTenantId) { try { - asyncTaskExecutor.execute(getRunnableWithCurrentFolioContext( - () -> syncPrimaryAffiliationService.syncPrimaryAffiliations(consortiumId, tenantId, centralTenantId))); + syncPrimaryAffiliationService.syncPrimaryAffiliations(consortiumId, tenantId, centralTenantId); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } catch (Exception e) { log.error("syncPrimaryAffiliations:: error syncing user primary affiliations", e); - service.updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); + tenantService.updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); throw e; } } @@ -81,13 +76,11 @@ public ResponseEntity syncPrimaryAffiliations(UUID consortiumId, String te public ResponseEntity createPrimaryAffiliations(UUID consortiumId, String tenantId, @NotNull String centralTenantId, SyncPrimaryAffiliationBody syncPrimaryAffiliationBody) { try { - var context = TenantContextUtils.prepareContextForTenant(centralTenantId, folioExecutionContext.getFolioModuleMetadata(), folioExecutionContext); - asyncTaskExecutor.execute(getRunnableWithFolioContext(context, - () -> syncPrimaryAffiliationService.createPrimaryUserAffiliations(consortiumId, centralTenantId, syncPrimaryAffiliationBody))); + syncPrimaryAffiliationService.createPrimaryUserAffiliations(consortiumId, centralTenantId, syncPrimaryAffiliationBody); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } catch (Exception e) { log.error("createPrimaryAffiliations:: error creating user primary affiliations", e); - service.updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); + tenantService.updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); throw e; } } diff --git a/src/main/java/org/folio/consortia/repository/TenantRepository.java b/src/main/java/org/folio/consortia/repository/TenantRepository.java index 69be4a3e..8b658e75 100644 --- a/src/main/java/org/folio/consortia/repository/TenantRepository.java +++ b/src/main/java/org/folio/consortia/repository/TenantRepository.java @@ -24,6 +24,7 @@ public interface TenantRepository extends JpaRepository { Optional findCentralTenant(); boolean existsByIsCentralTrue(); + @Query("SELECT CASE WHEN COUNT(t) > 0 THEN TRUE ELSE FALSE END FROM TenantEntity t " + "WHERE t.code = ?1 AND t.id != ?2") boolean existsByCodeForOtherTenant(String name, String tenantId); diff --git a/src/main/java/org/folio/consortia/service/ConsortiaConfigurationService.java b/src/main/java/org/folio/consortia/service/ConsortiaConfigurationService.java index ff8aefae..e0add0b1 100644 --- a/src/main/java/org/folio/consortia/service/ConsortiaConfigurationService.java +++ b/src/main/java/org/folio/consortia/service/ConsortiaConfigurationService.java @@ -1,6 +1,7 @@ package org.folio.consortia.service; import org.folio.consortia.domain.dto.ConsortiaConfiguration; +import org.folio.consortia.exception.ResourceAlreadyExistException; public interface ConsortiaConfigurationService { /** @@ -24,12 +25,16 @@ public interface ConsortiaConfigurationService { * This configuration will be stored in requested tenant schema * * @param centralTenantId id of central tenant for requested tenant + * @throws ResourceAlreadyExistException if configuration already exists */ - ConsortiaConfiguration createConfiguration(String centralTenantId); + ConsortiaConfiguration createConfiguration(String centralTenantId) throws ResourceAlreadyExistException; /** - * Check if there is any central tenant - * @return boolean value based one whether central tenant configuration exists or not + * Check if there exists a central tenant configuration; + * if not then save new configuration with central tenant id as value. + * This configuration will be stored in requested tenant schema + * + * @param centralTenantId id of central tenant for requested tenant */ - boolean isCentralTenantConfigurationExists(); + void createConfigurationIfNeeded(String centralTenantId); } diff --git a/src/main/java/org/folio/consortia/service/TenantManager.java b/src/main/java/org/folio/consortia/service/TenantManager.java new file mode 100644 index 00000000..6b60ee17 --- /dev/null +++ b/src/main/java/org/folio/consortia/service/TenantManager.java @@ -0,0 +1,51 @@ +package org.folio.consortia.service; + +import java.util.UUID; + +import org.folio.consortia.domain.dto.Tenant; +import org.folio.consortia.domain.dto.TenantCollection; + +public interface TenantManager { + + /** + * Gets tenant collection based on consortiumId. + * + * @param consortiumId the consortiumId + * @param limit the limit + * @param offset the offset + * @return tenant collection + */ + TenantCollection get(UUID consortiumId, Integer offset, Integer limit); + + /** + * Inserts single tenant based on consortiumId. + * Method checks whether requesting tenant is soft deleted or new tenant. + * For re-adding soft deleted tenant, + * tenant is_deleted flag will be changed to false and dummy user will be created in mod_users.user-tenants table + * For new tenant, all necessary actions will be done. + * + * @param consortiumId the consortiumId + * @param tenantDto the tenantDto + * @param adminUserId the id of admin_user + * @return tenantDto + */ + Tenant save(UUID consortiumId, UUID adminUserId, Tenant tenantDto); + + /** + * Updates single tenant based on consortiumId. + * + * @param consortiumId the consortiumId + * @param tenantId the tenantId + * @param tenantDto the tenantDto + * @return tenantDto + */ + Tenant update(UUID consortiumId, String tenantId, Tenant tenantDto); + + /** + * Deletes single tenant based on consortiumId. + * @param consortiumId the consortiumId + * @param tenantId the tenantId + */ + void delete(UUID consortiumId, String tenantId); + +} diff --git a/src/main/java/org/folio/consortia/service/TenantService.java b/src/main/java/org/folio/consortia/service/TenantService.java index 4a8c8ca4..55c0dacf 100644 --- a/src/main/java/org/folio/consortia/service/TenantService.java +++ b/src/main/java/org/folio/consortia/service/TenantService.java @@ -1,5 +1,7 @@ package org.folio.consortia.service; +import org.folio.consortia.domain.dto.User; +import org.folio.consortia.exception.ResourceAlreadyExistException; import org.folio.consortia.exception.ResourceNotFoundException; import java.util.List; import java.util.UUID; @@ -30,28 +32,40 @@ public interface TenantService { TenantCollection getAll(UUID consortiumId); /** - * Inserts single tenant based on consortiumId. - * Method checks whether requesting tenant is soft deleted or new tenant. - * For re-adding soft deleted tenant, - * tenant is_deleted flag will be changed to false and dummy user will be created in mod_users.user-tenants table - * For new tenant, all necessary actions will be done. + * Inserts single tenant + * + * @param tenantEntity the tenant entity + * @return tenantDto + */ + Tenant saveTenant(TenantEntity tenantEntity); + + /** + * Inserts single tenant based on consortiumId * * @param consortiumId the consortiumId * @param tenantDto the tenantDto - * @param adminUserId the id of admin_user * @return tenantDto */ - Tenant save(UUID consortiumId, UUID adminUserId, Tenant tenantDto); + Tenant saveTenant(UUID consortiumId, Tenant tenantDto); /** - * Updates single tenant based on consortiumId. + * Inserts single tenant based on consortiumId and setup status * * @param consortiumId the consortiumId - * @param tenantId the tenantId * @param tenantDto the tenantDto + * @param setupStatus the setup status * @return tenantDto */ - Tenant update(UUID consortiumId, String tenantId, Tenant tenantDto); + Tenant saveTenantDetails(UUID consortiumId, Tenant tenantDto, TenantDetails.SetupStatusEnum setupStatus); + + /** + * Inserts single user tenant based on consortiumId + * + * @param consortiumId the consortiumId + * @param user the user + * @param tenantDto the tenantDto + */ + void saveUserTenant(UUID consortiumId, User user, Tenant tenantDto); /** * Updates tenant's setup status. @@ -62,13 +76,6 @@ public interface TenantService { */ void updateTenantSetupStatus(String tenantId, String centralTenantId, SetupStatusEnum setupStatus); - /** - * Deletes single tenant based on consortiumId. - * @param consortiumId the consortiumId - * @param tenantId the tenantId - */ - void delete(UUID consortiumId, String tenantId); - /** * Gets tenant entity based on tenantId. * @@ -92,6 +99,18 @@ public interface TenantService { */ String getCentralTenantId(); + /** + * Checks if central tenant exists + * @return true if central tenant exists, false otherwise + */ + boolean centralTenantExists(); + + /** + * Checks if tenant with given name exists + * @throws ResourceAlreadyExistException in case if tenant with given name or code already exists + */ + void checkTenantUniqueNameAndCodeOrThrow(Tenant tenant); + /** * Check for tenant existence in consortia * @throws ResourceNotFoundException in case if tenants absence in consortia diff --git a/src/main/java/org/folio/consortia/service/impl/ConsortiaConfigurationServiceImpl.java b/src/main/java/org/folio/consortia/service/impl/ConsortiaConfigurationServiceImpl.java index b78e85f8..db53cd23 100644 --- a/src/main/java/org/folio/consortia/service/impl/ConsortiaConfigurationServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/ConsortiaConfigurationServiceImpl.java @@ -1,6 +1,8 @@ package org.folio.consortia.service.impl; import java.util.List; +import java.util.function.Supplier; + import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.consortia.domain.dto.ConsortiaConfiguration; @@ -37,8 +39,22 @@ public String getCentralTenantId(String requestTenantId) { } @Override - public ConsortiaConfiguration createConfiguration(String centralTenantId) { - checkAnyConsortiaConfigurationNotExistsOrThrow(); + public ConsortiaConfiguration createConfiguration(String centralTenantId) throws ResourceAlreadyExistException { + return createConfiguration(centralTenantId, () -> { + throw new ResourceAlreadyExistException(CONSORTIA_CONFIGURATION_EXIST_MSG_TEMPLATE); + }); + } + + @Override + public void createConfigurationIfNeeded(String centralTenantId) { + createConfiguration(centralTenantId, this::getConsortiaConfiguration); + } + + private ConsortiaConfiguration createConfiguration(String centralTenantId, Supplier supplierIfConfigExists) { + if (configurationRepository.count() > 0) { + log.info("createConfiguration:: Configuration already exists for central tenant: '{}'", centralTenantId); + return supplierIfConfigExists.get(); + } ConsortiaConfigurationEntity configuration = new ConsortiaConfigurationEntity(); configuration.setCentralTenantId(centralTenantId); return converter.convert(configurationRepository.save(configuration), ConsortiaConfiguration.class); @@ -53,14 +69,4 @@ private ConsortiaConfigurationEntity getConfiguration(String requestTenantId) { return configList.get(0); } - public boolean isCentralTenantConfigurationExists() { - return configurationRepository.count() > 0; - } - - private void checkAnyConsortiaConfigurationNotExistsOrThrow() { - if (configurationRepository.count() > 0) { - throw new ResourceAlreadyExistException(CONSORTIA_CONFIGURATION_EXIST_MSG_TEMPLATE); - } - } - } diff --git a/src/main/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImpl.java b/src/main/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImpl.java index 30276227..b552e0da 100644 --- a/src/main/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImpl.java @@ -1,5 +1,8 @@ package org.folio.consortia.service.impl; +import static org.folio.spring.scope.FolioExecutionScopeExecutionContextManager.getRunnableWithCurrentFolioContext; +import static org.folio.spring.scope.FolioExecutionScopeExecutionContextManager.getRunnableWithFolioContext; + import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -8,7 +11,6 @@ import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.folio.consortia.client.SyncPrimaryAffiliationClient; import org.folio.consortia.domain.dto.Personal; import org.folio.consortia.domain.dto.PrimaryAffiliationEvent; import org.folio.consortia.domain.dto.SyncPrimaryAffiliationBody; @@ -23,7 +25,12 @@ import org.folio.consortia.service.SyncPrimaryAffiliationService; import org.folio.consortia.service.TenantService; import org.folio.consortia.service.UserService; +import org.folio.consortia.utils.TenantContextUtils; +import org.folio.spring.FolioExecutionContext; import org.folio.spring.data.OffsetRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -35,12 +42,25 @@ public class SyncPrimaryAffiliationServiceImpl implements SyncPrimaryAffiliation private final UserService userService; private final TenantService tenantService; private final UserTenantRepository userTenantRepository; - private final SyncPrimaryAffiliationClient syncPrimaryAffiliationClient; private final LockService lockService; private final PrimaryAffiliationService createPrimaryAffiliationService; + private final FolioExecutionContext folioExecutionContext; + private final AsyncTaskExecutor asyncTaskExecutor; + + // Self reference to enable @Transactional method calls + private SyncPrimaryAffiliationServiceImpl self; + @Autowired + public void setSyncPrimaryAffiliationService(@Lazy SyncPrimaryAffiliationServiceImpl self) { + this.self = self; + } @Override public void syncPrimaryAffiliations(UUID consortiumId, String tenantId, String centralTenantId) { + asyncTaskExecutor.execute(getRunnableWithCurrentFolioContext( + () -> syncPrimaryAffiliationsInternal(consortiumId, tenantId, centralTenantId))); + } + + void syncPrimaryAffiliationsInternal(UUID consortiumId, String tenantId, String centralTenantId) { log.info("Start syncing user primary affiliations for tenant {}", tenantId); List users = new ArrayList<>(); try { @@ -50,15 +70,14 @@ public void syncPrimaryAffiliations(UUID consortiumId, String tenantId, String c tenantService.updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); } - try { - if (CollectionUtils.isNotEmpty(users)) { - SyncPrimaryAffiliationBody spab = buildSyncPrimaryAffiliationBody(tenantId, users); - syncPrimaryAffiliationClient.savePrimaryAffiliations(spab, consortiumId.toString(), tenantId, centralTenantId); + if (CollectionUtils.isNotEmpty(users)) { + try { + self.createPrimaryUserAffiliations(consortiumId, centralTenantId, buildSyncPrimaryAffiliationBody(tenantId, users)); + } catch (Exception e) { + log.error("syncPrimaryAffiliations:: error syncing user primary affiliations", e); + tenantService.updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); + throw e; } - } catch (Exception e) { - log.error("syncPrimaryAffiliations:: error syncing user primary affiliations", e); - tenantService.updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); - throw e; } } @@ -90,10 +109,15 @@ private SyncUser getSyncUser(User user, String tenantId) { return syncUser; } - @Transactional @Override - public void createPrimaryUserAffiliations(UUID consortiumId, String centralTenantId, - SyncPrimaryAffiliationBody syncPrimaryAffiliationBody) { + public void createPrimaryUserAffiliations(UUID consortiumId, String centralTenantId, SyncPrimaryAffiliationBody syncPrimaryAffiliationBody) { + var context = TenantContextUtils.prepareContextForTenant(centralTenantId, folioExecutionContext.getFolioModuleMetadata(), folioExecutionContext); + asyncTaskExecutor.execute(getRunnableWithFolioContext(context, + () -> self.createPrimaryUserAffiliationsInternal(consortiumId, centralTenantId, syncPrimaryAffiliationBody))); + } + + @Transactional + public void createPrimaryUserAffiliationsInternal(UUID consortiumId, String centralTenantId, SyncPrimaryAffiliationBody syncPrimaryAffiliationBody) { try { log.info("Start creating user primary affiliation for tenant {}", syncPrimaryAffiliationBody.getTenantId()); lockService.lockTenantSetupWithinTransaction(); diff --git a/src/main/java/org/folio/consortia/service/impl/TenantManagerImpl.java b/src/main/java/org/folio/consortia/service/impl/TenantManagerImpl.java new file mode 100644 index 00000000..01fcc413 --- /dev/null +++ b/src/main/java/org/folio/consortia/service/impl/TenantManagerImpl.java @@ -0,0 +1,270 @@ +package org.folio.consortia.service.impl; + +import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; +import static org.folio.consortia.service.impl.CustomFieldServiceImpl.ORIGINAL_TENANT_ID_CUSTOM_FIELD; +import static org.folio.consortia.service.impl.CustomFieldServiceImpl.ORIGINAL_TENANT_ID_NAME; +import static org.folio.consortia.utils.HelperUtils.checkIdenticalOrThrow; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import org.apache.commons.collections4.map.CaseInsensitiveMap; +import org.apache.commons.lang3.ObjectUtils; +import org.folio.consortia.client.UserTenantsClient; +import org.folio.consortia.domain.dto.Tenant; +import org.folio.consortia.domain.dto.TenantCollection; +import org.folio.consortia.domain.dto.TenantDetails; +import org.folio.consortia.domain.dto.User; +import org.folio.consortia.domain.dto.UserTenant; +import org.folio.consortia.domain.entity.TenantEntity; +import org.folio.consortia.exception.ResourceAlreadyExistException; +import org.folio.consortia.exception.ResourceNotFoundException; +import org.folio.consortia.service.CapabilitiesUserService; +import org.folio.consortia.service.CleanupService; +import org.folio.consortia.service.ConsortiaConfigurationService; +import org.folio.consortia.service.ConsortiumService; +import org.folio.consortia.service.CustomFieldService; +import org.folio.consortia.service.LockService; +import org.folio.consortia.service.SyncPrimaryAffiliationService; +import org.folio.consortia.service.TenantManager; +import org.folio.consortia.service.TenantService; +import org.folio.consortia.service.UserService; +import org.folio.spring.FolioExecutionContext; +import org.folio.spring.context.ExecutionContextBuilder; +import org.folio.spring.scope.FolioExecutionContextSetter; +import org.folio.spring.service.SystemUserScopedExecutionService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class TenantManagerImpl implements TenantManager { + + private static final String SHADOW_ADMIN_PERMISSION_SETS_FILE_PATH = "permissions/admin-user-permission-sets.csv"; + private static final String TENANTS_IDS_NOT_MATCHED_ERROR_MSG = "Request body tenantId and path param tenantId should be identical"; + + private static final String DUMMY_USERNAME = "dummy_user"; + + private final TenantService tenantService; + private final ConsortiumService consortiumService; + private final ConsortiaConfigurationService consortiaConfigurationService; + private final SyncPrimaryAffiliationService syncPrimaryAffiliationService; + private final UserService userService; + private final CapabilitiesUserService capabilitiesUserService; + private final CustomFieldService customFieldService; + private final CleanupService cleanupService; + private final LockService lockService; + private final UserTenantsClient userTenantsClient; + private final SystemUserScopedExecutionService systemUserScopedExecutionService; + private final ExecutionContextBuilder contextBuilder; + private final FolioExecutionContext folioExecutionContext; + + @Override + public TenantCollection get(UUID consortiumId, Integer offset, Integer limit) { + return tenantService.get(consortiumId, offset, limit); + } + + @Override + @Transactional + public Tenant save(UUID consortiumId, UUID adminUserId, Tenant tenantDto) { + log.info("save:: Trying to save a tenant with id={}, consortiumId={} and isCentral={}", tenantDto.getId(), + consortiumId, tenantDto.getIsCentral()); + validateConsortiumAndTenantForSaveOperation(consortiumId, tenantDto); + tenantService.checkTenantUniqueNameAndCodeOrThrow(tenantDto); + + createCustomFieldIfNeeded(tenantDto.getId()); + + var existingTenant = tenantService.getByTenantId(tenantDto.getId()); + + // checked whether tenant exists or not. + return existingTenant != null + ? reAddSoftDeletedTenant(consortiumId, existingTenant, tenantDto) + : addNewTenant(consortiumId, tenantDto, adminUserId); + } + + @Override + @Transactional + public Tenant update(UUID consortiumId, String tenantId, Tenant tenantDto) { + log.debug("update:: Trying to update tenant '{}' in consortium '{}'", tenantId, consortiumId); + var existedTenant = getTenantById(tenantId); + + validateTenantForUpdateOperation(consortiumId, tenantId, tenantDto, existedTenant); + // isDeleted flag cannot be changed by put request + tenantDto.setIsDeleted(existedTenant.getIsDeleted()); + return tenantService.saveTenant(consortiumId, tenantDto); + } + + @Override + @Transactional + public void delete(UUID consortiumId, String tenantId) { + consortiumService.checkConsortiumExistsOrThrow(consortiumId); + var tenant = getTenantById(tenantId); + validateTenantForDeleteOperation(tenant); + + tenant.setIsDeleted(true); + // clean publish coordinator tables first, because after tenant removal it will be ignored by cleanup service + cleanupService.clearPublicationTables(); + tenantService.saveTenant(tenant); + + try (var ignored = new FolioExecutionContextSetter(contextBuilder.buildContext(tenantId))) { + userTenantsClient.deleteUserTenants(); + } + } + + private void createCustomFieldIfNeeded(String tenant) { + systemUserScopedExecutionService.executeSystemUserScoped(tenant, () -> { + if (isNotEmpty(customFieldService.getCustomFieldByName(ORIGINAL_TENANT_ID_NAME))) { + log.info("createOriginalTenantIdCustomField:: custom-field already available in tenant {} with name {}", + tenant, ORIGINAL_TENANT_ID_NAME); + } else { + customFieldService.createCustomField(ORIGINAL_TENANT_ID_CUSTOM_FIELD); + } + return null; + } + ); + } + + private Tenant reAddSoftDeletedTenant(UUID consortiumId, TenantEntity existingTenant, Tenant tenantDto) { + log.info("reAddSoftDeletedTenant:: Re-adding soft deleted tenant with id={}", tenantDto.getId()); + validateExistingTenant(existingTenant); + + tenantDto.setIsDeleted(false); + var savedTenant = tenantService.saveTenantDetails(consortiumId, tenantDto, TenantDetails.SetupStatusEnum.COMPLETED); + + String centralTenantId = tenantService.getCentralTenantId(); + try (var ignored = new FolioExecutionContextSetter(contextBuilder.buildContext(tenantDto.getId()))) { + if (isUserTenantsEmpty()) { + createUserTenantWithDummyUser(tenantDto.getId(), centralTenantId, consortiumId); + log.info("reAddSoftDeletedTenant:: Dummy user re-created in user-tenants table"); + } + } catch (Exception e) { + log.error("Failed to create dummy user with centralTenantId: {}, tenant: {}" + + " and error message: {}", centralTenantId, tenantDto.getId(), e.getMessage(), e); + } + return savedTenant; + } + + private Tenant addNewTenant(UUID consortiumId, Tenant tenantDto, UUID adminUserId) { + log.info("addNewTenant:: Creating new tenant with id={}, consortiumId={}, and adminUserId={}", + tenantDto.getId(), consortiumId, adminUserId); + + lockService.lockTenantSetupWithinTransaction(); + tenantDto.setIsDeleted(false); + Tenant savedTenant = tenantService.saveTenantDetails(consortiumId, tenantDto, TenantDetails.SetupStatusEnum.IN_PROGRESS); + + // save admin user tenant association for non-central tenant + String centralTenantId; + User shadowAdminUser = null; + if (tenantDto.getIsCentral()) { + centralTenantId = tenantDto.getId(); + } else { + checkAdminUserIdPresentOrThrow(adminUserId); + centralTenantId = tenantService.getCentralTenantId(); + shadowAdminUser = userService.prepareShadowUser(adminUserId, folioExecutionContext.getTenantId()); + tenantService.saveUserTenant(consortiumId, shadowAdminUser, tenantDto); + } + + var finalShadowAdminUser = shadowAdminUser; + // switch to context of the desired tenant and apply all necessary setup + + var allHeaders = new CaseInsensitiveMap<>(folioExecutionContext.getOkapiHeaders()); + allHeaders.put("x-okapi-tenant", List.of(tenantDto.getId())); + try (var ignored = new FolioExecutionContextSetter(folioExecutionContext.getFolioModuleMetadata(), allHeaders)) { + consortiaConfigurationService.createConfigurationIfNeeded(centralTenantId); + if (!tenantDto.getIsCentral() && isUserTenantsEmpty()) { + createUserTenantWithDummyUser(tenantDto.getId(), centralTenantId, consortiumId); + createShadowAdminWithPermissions(finalShadowAdminUser); + log.info("save:: shadow admin user '{}' with permissions was created in tenant '{}'", finalShadowAdminUser.getId(), tenantDto.getId()); + } + + syncPrimaryAffiliationService.syncPrimaryAffiliations(consortiumId, tenantDto.getId(), centralTenantId); + } + log.info("save:: saved consortia configuration with centralTenantId={} by tenantId={} context", centralTenantId, tenantDto.getId()); + return savedTenant; + } + + private TenantEntity getTenantById(String tenantId) { + var tenant = tenantService.getByTenantId(tenantId); + if (Objects.isNull(tenant)) { + throw new ResourceNotFoundException("id", tenantId); + } + return tenant; + } + + private boolean isUserTenantsEmpty() { + return userTenantsClient.getUserTenants().getTotalRecords().equals(0); + } + + /** + * Dummy user will be used to support cross-tenant requests checking in mod-authtoken, + * if user-tenant table contains some record in institutional tenant - it means mod-consortia enabled for + * this tenant and will allow cross-tenant request. + * + * @param tenantId tenant id + * @param centralTenantId central tenant id + * @param consortiumId consortium id + */ + private void createUserTenantWithDummyUser(String tenantId, String centralTenantId, UUID consortiumId) { + UserTenant userTenant = new UserTenant(); + userTenant.setId(UUID.randomUUID()); + userTenant.setTenantId(tenantId); + userTenant.setUserId(UUID.randomUUID()); + userTenant.setUsername(DUMMY_USERNAME); + userTenant.setCentralTenantId(centralTenantId); + userTenant.setConsortiumId(consortiumId); + + log.info("Creating userTenant with dummy user with id {}.", userTenant.getId()); + userTenantsClient.postUserTenant(userTenant); + } + + private void validateTenantForUpdateOperation(UUID consortiumId, String tenantId, Tenant tenantDto, TenantEntity existedTenant) { + consortiumService.checkConsortiumExistsOrThrow(consortiumId); + checkIdenticalOrThrow(tenantId, tenantDto.getId(), TENANTS_IDS_NOT_MATCHED_ERROR_MSG); + if (ObjectUtils.notEqual(tenantDto.getIsCentral(), existedTenant.getIsCentral())) { + throw new IllegalArgumentException(String.format("'isCentral' field cannot be changed. It should be '%s'", existedTenant.getIsCentral())); + } + } + + private void validateConsortiumAndTenantForSaveOperation(UUID consortiumId, Tenant tenantDto) { + consortiumService.checkConsortiumExistsOrThrow(consortiumId); + if (tenantDto.getIsCentral() && tenantService.centralTenantExists()) { + throw new ResourceAlreadyExistException("isCentral", "true"); + } + } + + private void validateExistingTenant(TenantEntity existingTenant) { + if (Boolean.FALSE.equals(existingTenant.getIsDeleted())) { + throw new ResourceAlreadyExistException("id", existingTenant.getId()); + } + } + + private void validateTenantForDeleteOperation(TenantEntity tenant) { + if (Boolean.TRUE.equals(tenant.getIsDeleted())) { + throw new IllegalArgumentException(String.format("Tenant [%s] has already been soft deleted.", tenant.getId())); + } + if (Boolean.TRUE.equals(tenant.getIsCentral())) { + throw new IllegalArgumentException(String.format("Central tenant [%s] cannot be deleted.", tenant.getId())); + } + } + + private void checkAdminUserIdPresentOrThrow(UUID adminUserId) { + if (Objects.isNull(adminUserId)) { + log.warn("checkAdminUserIdPresentOrThrow:: adminUserId is not present"); + throw new IllegalArgumentException("Required request parameter 'adminUserId' for method parameter type UUID is not present"); + } + } + + private void createShadowAdminWithPermissions(User user) { + User userOptional = userService.getById(UUID.fromString(user.getId())); + if (Objects.isNull(userOptional.getId())) { + userOptional = userService.createUser(user); + } + capabilitiesUserService.createWithPermissionSetsFromFile(userOptional.getId(), SHADOW_ADMIN_PERMISSION_SETS_FILE_PATH); + } + +} diff --git a/src/main/java/org/folio/consortia/service/impl/TenantServiceImpl.java b/src/main/java/org/folio/consortia/service/impl/TenantServiceImpl.java index f36d7640..1ea09c63 100644 --- a/src/main/java/org/folio/consortia/service/impl/TenantServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/TenantServiceImpl.java @@ -1,28 +1,15 @@ package org.folio.consortia.service.impl; -import static org.apache.commons.lang3.ObjectUtils.isNotEmpty; -import static org.folio.consortia.service.impl.CustomFieldServiceImpl.ORIGINAL_TENANT_ID_CUSTOM_FIELD; -import static org.folio.consortia.service.impl.CustomFieldServiceImpl.ORIGINAL_TENANT_ID_NAME; -import static org.folio.consortia.utils.HelperUtils.checkIdenticalOrThrow; - import java.util.List; -import java.util.Objects; import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.map.CaseInsensitiveMap; -import org.apache.commons.lang3.ObjectUtils; -import org.folio.consortia.client.ConsortiaConfigurationClient; -import org.folio.consortia.client.SyncPrimaryAffiliationClient; -import org.folio.consortia.client.UserTenantsClient; -import org.folio.consortia.domain.dto.ConsortiaConfiguration; import org.folio.consortia.domain.dto.Tenant; import org.folio.consortia.domain.dto.TenantCollection; import org.folio.consortia.domain.dto.TenantDetails; import org.folio.consortia.domain.dto.TenantDetails.SetupStatusEnum; import org.folio.consortia.domain.dto.User; -import org.folio.consortia.domain.dto.UserTenant; import org.folio.consortia.domain.entity.TenantDetailsEntity; import org.folio.consortia.domain.entity.TenantEntity; import org.folio.consortia.domain.entity.UserTenantEntity; @@ -31,19 +18,12 @@ import org.folio.consortia.repository.TenantDetailsRepository; import org.folio.consortia.repository.TenantRepository; import org.folio.consortia.repository.UserTenantRepository; -import org.folio.consortia.service.CleanupService; import org.folio.consortia.service.ConsortiumService; -import org.folio.consortia.service.CustomFieldService; -import org.folio.consortia.service.LockService; -import org.folio.consortia.service.CapabilitiesUserService; import org.folio.consortia.service.TenantService; -import org.folio.consortia.service.UserService; import org.folio.consortia.utils.TenantContextUtils; import org.folio.spring.FolioExecutionContext; -import org.folio.spring.context.ExecutionContextBuilder; import org.folio.spring.data.OffsetRequest; import org.folio.spring.scope.FolioExecutionContextSetter; -import org.folio.spring.service.SystemUserScopedExecutionService; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -55,27 +35,12 @@ @RequiredArgsConstructor public class TenantServiceImpl implements TenantService { - private static final String SHADOW_ADMIN_PERMISSION_SETS_FILE_PATH = "permissions/admin-user-permission-sets.csv"; - private static final String TENANTS_IDS_NOT_MATCHED_ERROR_MSG = "Request body tenantId and path param tenantId should be identical"; - - private static final String DUMMY_USERNAME = "dummy_user"; - private final TenantRepository tenantRepository; - private final TenantDetailsRepository tenantDetailsRepository; private final UserTenantRepository userTenantRepository; + private final TenantDetailsRepository tenantDetailsRepository; private final ConversionService converter; private final ConsortiumService consortiumService; private final FolioExecutionContext folioExecutionContext; - private final ConsortiaConfigurationClient configurationClient; - private final CapabilitiesUserService capabilitiesUserService; - private final UserService userService; - private final ExecutionContextBuilder contextBuilder; - private final UserTenantsClient userTenantsClient; - private final SyncPrimaryAffiliationClient syncPrimaryAffiliationClient; - private final CleanupService cleanupService; - private final LockService lockService; - private final SystemUserScopedExecutionService systemUserScopedExecutionService; - private final CustomFieldService customFieldService; @Override public TenantCollection get(UUID consortiumId, Integer offset, Integer limit) { @@ -119,102 +84,37 @@ public TenantEntity getByTenantId(String tenantId) { } @Override - @Transactional - public Tenant save(UUID consortiumId, UUID adminUserId, Tenant tenantDto) { - log.info("save:: Trying to save a tenant with id={}, consortiumId={} and isCentral={}", tenantDto.getId(), - consortiumId, tenantDto.getIsCentral()); - validateConsortiumAndTenantForSaveOperation(consortiumId, tenantDto); - validateCodeAndNameUniqueness(tenantDto); - - createCustomFieldIdNeeded(tenantDto.getId()); - - var existingTenant = tenantRepository.findById(tenantDto.getId()); - - // checked whether tenant exists or not. - return existingTenant.isPresent() ? reAddSoftDeletedTenant(consortiumId, existingTenant.get(), tenantDto) - : addNewTenant(consortiumId, tenantDto, adminUserId); + public boolean centralTenantExists() { + return tenantRepository.existsByIsCentralTrue(); } - private void createCustomFieldIdNeeded(String tenant) { - systemUserScopedExecutionService.executeSystemUserScoped(tenant, () -> { - if (isNotEmpty(customFieldService.getCustomFieldByName(ORIGINAL_TENANT_ID_NAME))) { - log.info("createOriginalTenantIdCustomField:: custom-field already available in tenant {} with name {}", - tenant, ORIGINAL_TENANT_ID_NAME); - } else { - customFieldService.createCustomField(ORIGINAL_TENANT_ID_CUSTOM_FIELD); - } - return null; - } - ); + @Override + public Tenant saveTenant(TenantEntity tenantEntity) { + log.debug("saveTenant:: Trying to save tenant with consoritumId={} and tenant with id={}", + tenantEntity.getConsortiumId(), tenantEntity.getId()); + TenantEntity savedTenant = tenantRepository.save(tenantEntity); + log.info("saveTenant: Tenant '{}' successfully saved", savedTenant.getId()); + return converter.convert(savedTenant, Tenant.class); } - private Tenant reAddSoftDeletedTenant(UUID consortiumId, TenantEntity existingTenant, Tenant tenantDto) { - log.info("reAddSoftDeletedTenant:: Re-adding soft deleted tenant with id={}", tenantDto.getId()); - validateExistingTenant(existingTenant); - - tenantDto.setIsDeleted(false); - var savedTenant = saveTenant(consortiumId, tenantDto, SetupStatusEnum.COMPLETED); - - String centralTenantId = getCentralTenantId(); - try (var ignored = new FolioExecutionContextSetter(contextBuilder.buildContext(tenantDto.getId()))) { - createUserTenantWithDummyUser(tenantDto.getId(), centralTenantId, consortiumId); - log.info("reAddSoftDeletedTenant:: Dummy user re-created in user-tenants table"); - } catch (Exception e) { - log.error("Failed to create dummy user with centralTenantId: {}, tenant: {}" + - " and error message: {}", centralTenantId, tenantDto.getId(), e.getMessage(), e); - } - return savedTenant; + @Override + public Tenant saveTenant(UUID consortiumId, Tenant tenantDto) { + return saveTenant(toTenantEntity(consortiumId, tenantDto)); } - private Tenant addNewTenant(UUID consortiumId, Tenant tenantDto, UUID adminUserId) { - log.info("addNewTenant:: Creating new tenant with id={}, consortiumId={}, and adminUserId={}", - tenantDto.getId(), consortiumId, adminUserId); - - lockService.lockTenantSetupWithinTransaction(); - tenantDto.setIsDeleted(false); - Tenant savedTenant = saveTenant(consortiumId, tenantDto, SetupStatusEnum.IN_PROGRESS); - - // save admin user tenant association for non-central tenant - String centralTenantId; - User shadowAdminUser = null; - if (tenantDto.getIsCentral()) { - centralTenantId = tenantDto.getId(); - } else { - checkAdminUserIdPresentOrThrow(adminUserId); - centralTenantId = getCentralTenantId(); - shadowAdminUser = userService.prepareShadowUser(adminUserId, folioExecutionContext.getTenantId()); - userTenantRepository.save(createUserTenantEntity(consortiumId, shadowAdminUser, tenantDto)); - } - - var finalShadowAdminUser = shadowAdminUser; - // switch to context of the desired tenant and apply all necessary setup - - var allHeaders = new CaseInsensitiveMap<>(folioExecutionContext.getOkapiHeaders()); - allHeaders.put("x-okapi-tenant", List.of(tenantDto.getId())); - try (var ignored = new FolioExecutionContextSetter(folioExecutionContext.getFolioModuleMetadata(), allHeaders)) { - configurationClient.saveConfiguration(createConsortiaConfigurationBody(centralTenantId)); - if (!tenantDto.getIsCentral()) { - createUserTenantWithDummyUser(tenantDto.getId(), centralTenantId, consortiumId); - createShadowAdminWithPermissions(finalShadowAdminUser); - log.info("save:: shadow admin user '{}' with permissions was created in tenant '{}'", finalShadowAdminUser.getId(), tenantDto.getId()); - } - syncPrimaryAffiliationClient.syncPrimaryAffiliations(consortiumId.toString(), tenantDto.getId(), centralTenantId); - } - log.info("save:: saved consortia configuration with centralTenantId={} by tenantId={} context", centralTenantId, tenantDto.getId()); - return savedTenant; + @Override + public Tenant saveTenantDetails(UUID consortiumId, Tenant tenantDto, TenantDetails.SetupStatusEnum setupStatus) { + log.debug("saveTenant:: Trying to save tenant with consoritumId={} and tenant with id={}, setupStatus={}", + consortiumId, tenantDto, setupStatus); + TenantDetailsEntity entity = toTenantDetailsEntity(consortiumId, tenantDto, setupStatus); + TenantDetailsEntity savedTenant = tenantDetailsRepository.save(entity); + log.info("saveTenant: Tenant '{}' successfully saved, setupStatus={}", savedTenant.getId(), savedTenant.getSetupStatus()); + return converter.convert(savedTenant, Tenant.class); } @Override - @Transactional - public Tenant update(UUID consortiumId, String tenantId, Tenant tenantDto) { - log.debug("update:: Trying to update tenant '{}' in consortium '{}'", tenantId, consortiumId); - var existedTenant = tenantRepository.findById(tenantId) - .orElseThrow(() -> new ResourceNotFoundException("id", tenantId)); - - validateTenantForUpdateOperation(consortiumId, tenantId, tenantDto, existedTenant); - // isDeleted flag cannot be changed by put request - tenantDto.setIsDeleted(existedTenant.getIsDeleted()); - return updateTenant(consortiumId, tenantDto); + public void saveUserTenant(UUID consortiumId, User user, Tenant tenant) { + userTenantRepository.save(createUserTenantEntity(consortiumId, user, tenant)); } @Override @@ -228,66 +128,15 @@ public void updateTenantSetupStatus(String tenantId, String centralTenantId, Set } @Override - @Transactional - public void delete(UUID consortiumId, String tenantId) { - consortiumService.checkConsortiumExistsOrThrow(consortiumId); - var tenant = tenantRepository.findById(tenantId); - - if (tenant.isEmpty()) { - throw new ResourceNotFoundException("id", tenantId); + public void checkTenantUniqueNameAndCodeOrThrow(Tenant tenant) { + if (tenantRepository.existsByNameForOtherTenant(tenant.getName(), tenant.getId())) { + throw new ResourceAlreadyExistException("name", tenant.getName()); } - - validateTenantForDeleteOperation(tenant.get()); - - var softDeletedTenant = tenant.get(); - softDeletedTenant.setIsDeleted(true); - // clean publish coordinator tables first, because after tenant removal it will be ignored by cleanup service - cleanupService.clearPublicationTables(); - tenantRepository.save(softDeletedTenant); - - try (var ignored = new FolioExecutionContextSetter(contextBuilder.buildContext(tenantId))) { - userTenantsClient.deleteUserTenants(); + if (tenantRepository.existsByCodeForOtherTenant(tenant.getCode(), tenant.getId())) { + throw new ResourceAlreadyExistException("code", tenant.getCode()); } } - private Tenant saveTenant(UUID consortiumId, Tenant tenantDto, SetupStatusEnum setupStatus) { - log.debug("saveTenant:: Trying to save tenant with consoritumId={} and tenant with id={}, setupStatus={}", - consortiumId, tenantDto, setupStatus); - TenantDetailsEntity entity = toTenantDetailsEntity(consortiumId, tenantDto, setupStatus); - TenantDetailsEntity savedTenant = tenantDetailsRepository.save(entity); - log.info("saveTenant: Tenant '{}' successfully saved, setupStatus={}", savedTenant.getId(), savedTenant.getSetupStatus()); - return converter.convert(savedTenant, Tenant.class); - } - - private Tenant updateTenant(UUID consortiumId, Tenant tenantDto) { - TenantEntity entity = toTenantEntity(consortiumId, tenantDto); - TenantEntity updatedTenant = tenantRepository.save(entity); - log.info("updateTenant:: Tenant '{}' successfully updated", updatedTenant.getId()); - return converter.convert(updatedTenant, Tenant.class); - } - - /** - * Dummy user will be used to support cross-tenant requests checking in mod-authtoken, - * if user-tenant table contains some record in institutional tenant - it means mod-consortia enabled for - * this tenant and will allow cross-tenant request. - * - * @param tenantId tenant id - * @param centralTenantId central tenant id - * @param consortiumId consortium id - */ - private void createUserTenantWithDummyUser(String tenantId, String centralTenantId, UUID consortiumId) { - UserTenant userTenant = new UserTenant(); - userTenant.setId(UUID.randomUUID()); - userTenant.setTenantId(tenantId); - userTenant.setUserId(UUID.randomUUID()); - userTenant.setUsername(DUMMY_USERNAME); - userTenant.setCentralTenantId(centralTenantId); - userTenant.setConsortiumId(consortiumId); - - log.info("Creating userTenant with dummy user with id {}.", userTenant.getId()); - userTenantsClient.postUserTenant(userTenant); - } - @Override public void checkTenantExistsOrThrow(String tenantId) { if (!tenantRepository.existsById(tenantId)) { @@ -311,52 +160,6 @@ public void checkTenantsAndConsortiumExistsOrThrow(UUID consortiumId, List> constraintViolations = new HashSet<>(); constraintViolations.add(mock(ConstraintViolation.class)); diff --git a/src/test/java/org/folio/consortia/service/ConsortiaConfigurationServiceTest.java b/src/test/java/org/folio/consortia/service/ConsortiaConfigurationServiceTest.java index 528acab7..fe7d083d 100644 --- a/src/test/java/org/folio/consortia/service/ConsortiaConfigurationServiceTest.java +++ b/src/test/java/org/folio/consortia/service/ConsortiaConfigurationServiceTest.java @@ -25,7 +25,9 @@ import static org.folio.consortia.support.EntityUtils.createConsortiaConfiguration; import static org.folio.consortia.support.EntityUtils.createConsortiaConfigurationEntity; +import static org.folio.consortia.support.EntityUtils.createOkapiHeaders; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -86,6 +88,19 @@ void shouldSaveConfigValue() { verify(configurationRepository, times(1)).save(any()); } + @Test + void shouldReturnConfigValueWhenSavingIfExists() { + ConsortiaConfigurationEntity configuration = createConsortiaConfigurationEntity(CENTRAL_TENANT_ID); + + when(folioExecutionContext.getOkapiHeaders()).thenReturn(createOkapiHeaders()); + when(configurationRepository.findAll()).thenReturn(List.of(configuration)); + when(configurationRepository.count()).thenReturn(1L); + + configurationService.createConfigurationIfNeeded(CENTRAL_TENANT_ID); + + verify(configurationRepository, never()).save(configuration); + } + @Test void shouldThrowResourceAlreadyExistExceptionErrorWhileSavingConfigValue() { ConsortiaConfigurationEntity configuration = createConsortiaConfigurationEntity(CENTRAL_TENANT_ID); diff --git a/src/test/java/org/folio/consortia/service/TenantServiceTest.java b/src/test/java/org/folio/consortia/service/TenantManagerTest.java similarity index 74% rename from src/test/java/org/folio/consortia/service/TenantServiceTest.java rename to src/test/java/org/folio/consortia/service/TenantManagerTest.java index 0f779ef7..58c12bb4 100644 --- a/src/test/java/org/folio/consortia/service/TenantServiceTest.java +++ b/src/test/java/org/folio/consortia/service/TenantManagerTest.java @@ -1,13 +1,14 @@ package org.folio.consortia.service; import static org.folio.consortia.service.impl.CustomFieldServiceImpl.ORIGINAL_TENANT_ID_CUSTOM_FIELD; +import static org.folio.consortia.support.EntityUtils.CENTRAL_TENANT_ID; import static org.folio.consortia.support.EntityUtils.TENANT_ID; -import static org.folio.consortia.support.EntityUtils.createConsortiaConfiguration; import static org.folio.consortia.support.EntityUtils.createOkapiHeaders; import static org.folio.consortia.support.EntityUtils.createTenant; import static org.folio.consortia.support.EntityUtils.createTenantDetailsEntity; import static org.folio.consortia.support.EntityUtils.createTenantEntity; import static org.folio.consortia.support.EntityUtils.createUser; +import static org.folio.consortia.support.EntityUtils.getFolioExecutionContext; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -32,19 +33,20 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.Callable; -import org.folio.consortia.client.ConsortiaConfigurationClient; -import org.folio.consortia.client.SyncPrimaryAffiliationClient; import org.folio.consortia.client.UserTenantsClient; import org.folio.consortia.domain.dto.Tenant; import org.folio.consortia.domain.dto.TenantDetails; import org.folio.consortia.domain.dto.User; +import org.folio.consortia.domain.dto.UserTenantCollection; import org.folio.consortia.domain.entity.TenantDetailsEntity; import org.folio.consortia.domain.entity.TenantEntity; +import org.folio.consortia.domain.entity.UserTenantEntity; import org.folio.consortia.exception.ResourceAlreadyExistException; import org.folio.consortia.exception.ResourceNotFoundException; import org.folio.consortia.repository.TenantDetailsRepository; import org.folio.consortia.repository.TenantRepository; import org.folio.consortia.repository.UserTenantRepository; +import org.folio.consortia.service.impl.TenantManagerImpl; import org.folio.consortia.service.impl.TenantServiceImpl; import org.folio.spring.FolioExecutionContext; import org.folio.spring.context.ExecutionContextBuilder; @@ -52,9 +54,10 @@ import org.folio.spring.integration.XOkapiHeaders; import org.folio.spring.service.SystemUserScopedExecutionService; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; @@ -65,12 +68,10 @@ @SpringBootTest @EnableAutoConfiguration(exclude = BatchAutoConfiguration.class) @EntityScan(basePackageClasses = TenantEntity.class) -class TenantServiceTest { +class TenantManagerTest { - private final static String CONSORTIUM_ID = "7698e46-c3e3-11ed-afa1-0242ac120002"; + private static final UUID CONSORTIUM_ID = UUID.fromString("7698e46-c3e3-11ed-afa1-0242ac120002"); - @InjectMocks - private TenantServiceImpl tenantService; @Mock private TenantRepository tenantRepository; @Mock @@ -81,10 +82,10 @@ class TenantServiceTest { private ConversionService conversionService; @Mock private ConsortiumService consortiumService; + @Spy + private FolioExecutionContext folioExecutionContext = getFolioExecutionContext(); @Mock - private FolioExecutionContext folioExecutionContext; - @Mock - private ConsortiaConfigurationClient configurationClient; + private ConsortiaConfigurationService consortiaConfigurationService; @Mock private CapabilitiesUserService capabilitiesUserService; @Mock @@ -94,7 +95,7 @@ class TenantServiceTest { @Mock private UserTenantsClient userTenantsClient; @Mock - private SyncPrimaryAffiliationClient syncPrimaryAffiliationClient; + private SyncPrimaryAffiliationService syncPrimaryAffiliationService; @Mock private CleanupService cleanupService; @Mock @@ -104,6 +105,16 @@ class TenantServiceTest { @Mock private CustomFieldService customFieldService; + private TenantManager tenantManager; + private TenantService tenantService; + + @BeforeEach + void setUp() { + tenantService = new TenantServiceImpl(tenantRepository, userTenantRepository, tenantDetailsRepository, conversionService, consortiumService, folioExecutionContext); + tenantManager = new TenantManagerImpl(tenantService, consortiumService, consortiaConfigurationService, syncPrimaryAffiliationService, userService, capabilitiesUserService, + customFieldService, cleanupService, lockService, userTenantsClient, systemUserScopedExecutionService, executionContextBuilder, folioExecutionContext); + } + @Test void shouldGetTenantList() { int offset = 0; @@ -141,7 +152,6 @@ void shouldGetAllTenantList() { @Test void shouldSaveNotCentralTenantWithNewUserAndPermissions() { - UUID consortiumId = UUID.fromString(CONSORTIUM_ID); TenantDetailsEntity localTenantDetailsEntity = createTenantDetailsEntity("ABC1", "TestName1"); Tenant tenant = createTenant("TestID", "Test"); TenantEntity centralTenant = createTenantEntity("diku", "diku"); @@ -153,8 +163,9 @@ void shouldSaveNotCentralTenantWithNewUserAndPermissions() { when(tenantRepository.existsById(any())).thenReturn(false); when(tenantRepository.findCentralTenant()).thenReturn(Optional.of(centralTenant)); when(tenantDetailsRepository.save(any(TenantDetailsEntity.class))).thenReturn(localTenantDetailsEntity); - doNothing().when(configurationClient).saveConfiguration(createConsortiaConfiguration(TENANT_ID)); + doNothing().when(consortiaConfigurationService).createConfigurationIfNeeded(TENANT_ID); doNothing().when(userTenantsClient).postUserTenant(any()); + when(userTenantsClient.getUserTenants()).thenReturn(new UserTenantCollection(List.of(), 0)); when(conversionService.convert(localTenantDetailsEntity, Tenant.class)).thenReturn(tenant); when(folioExecutionContext.getTenantId()).thenReturn(TENANT_ID); when(customFieldService.getCustomFieldByName("originalTenantId")).thenReturn(ORIGINAL_TENANT_ID_CUSTOM_FIELD); @@ -164,11 +175,11 @@ void shouldSaveNotCentralTenantWithNewUserAndPermissions() { return action.call(); }); - var tenant1 = tenantService.save(consortiumId, UUID.fromString(adminUser.getId()), tenant); + var tenant1 = tenantManager.save(CONSORTIUM_ID, UUID.fromString(adminUser.getId()), tenant); verify(userService, times(1)).prepareShadowUser(UUID.fromString(adminUser.getId()), "diku"); verify(userTenantRepository, times(1)).save(any()); - verify(configurationClient).saveConfiguration(any()); + verify(consortiaConfigurationService).createConfigurationIfNeeded(any()); verify(userTenantsClient).postUserTenant(any()); verify(userService, times(1)).createUser(any()); verify(lockService).lockTenantSetupWithinTransaction(); @@ -180,7 +191,6 @@ void shouldSaveNotCentralTenantWithNewUserAndPermissions() { @Test void shouldSaveCentralTenantWithExistingAndPermissions() { - UUID consortiumId = UUID.fromString(CONSORTIUM_ID); TenantDetailsEntity tenantDetailsEntity = createTenantDetailsEntity("ABC1", "TestName1"); Tenant tenant = createTenant("TestID", "Test", true); TenantEntity centralTenant = createTenantEntity(TENANT_ID); @@ -191,7 +201,7 @@ void shouldSaveCentralTenantWithExistingAndPermissions() { when(tenantRepository.existsById(any())).thenReturn(false); when(tenantRepository.findCentralTenant()).thenReturn(Optional.of(centralTenant)); when(tenantDetailsRepository.save(any(TenantDetailsEntity.class))).thenReturn(tenantDetailsEntity); - doNothing().when(configurationClient).saveConfiguration(createConsortiaConfiguration(TENANT_ID)); + doNothing().when(consortiaConfigurationService).createConfigurationIfNeeded(TENANT_ID); doNothing().when(userTenantsClient).postUserTenant(any()); when(conversionService.convert(tenantDetailsEntity, Tenant.class)).thenReturn(tenant); when(folioExecutionContext.getTenantId()).thenReturn("diku"); @@ -207,9 +217,9 @@ void shouldSaveCentralTenantWithExistingAndPermissions() { return action.call(); }); - var tenant1 = tenantService.save(consortiumId, UUID.randomUUID(), tenant); + var tenant1 = tenantManager.save(CONSORTIUM_ID, UUID.randomUUID(), tenant); - verify(configurationClient).saveConfiguration(any()); + verify(consortiaConfigurationService).createConfigurationIfNeeded(any()); verify(lockService).lockTenantSetupWithinTransaction(); verify(userService, never()).prepareShadowUser(any(), any()); @@ -225,7 +235,6 @@ void shouldSaveCentralTenantWithExistingAndPermissions() { @Test void shouldReAddSoftDeletedTenant() { - UUID consortiumId = UUID.fromString(CONSORTIUM_ID); Tenant newTenant = createTenant("TestID", "Test", false); TenantEntity existedTenant = createTenantEntity("TestID"); existedTenant.setIsDeleted(true); @@ -237,14 +246,14 @@ void shouldReAddSoftDeletedTenant() { when(tenantDetailsRepository.save(any(TenantDetailsEntity.class))).thenReturn(savedTenantDetailsEntity); doNothing().when(tenantDetailsRepository).setSetupStatusByTenantId(TenantDetails.SetupStatusEnum.COMPLETED, newTenant.getId()); doNothing().when(userTenantsClient).postUserTenant(any()); - doNothing().when(userTenantsClient).postUserTenant(any()); + when(userTenantsClient.getUserTenants()).thenReturn(new UserTenantCollection(List.of(), 0)); when(conversionService.convert(savedTenantDetailsEntity, Tenant.class)).thenReturn(newTenant); doReturn(folioExecutionContext).when(executionContextBuilder).buildContext(anyString()); mockOkapiHeaders(); - var actualTenant = tenantService.save(consortiumId, UUID.randomUUID(), newTenant); + var actualTenant = tenantManager.save(CONSORTIUM_ID, UUID.randomUUID(), newTenant); - verifyNoInteractions(configurationClient); + verifyNoInteractions(consortiaConfigurationService); verifyNoInteractions(lockService); verifyNoInteractions(userService); @@ -266,7 +275,7 @@ void shouldUpdateTenant() { when(conversionService.convert(existingTenant, Tenant.class)).thenReturn(tenant); mockOkapiHeaders(); - var tenant1 = tenantService.update(UUID.fromString(CONSORTIUM_ID), tenant.getId(), tenant); + var tenant1 = tenantManager.update(CONSORTIUM_ID, tenant.getId(), tenant); Assertions.assertEquals(tenant.getId(), tenant1.getId()); Assertions.assertEquals("TestName2", tenant1.getName()); } @@ -285,7 +294,7 @@ void shouldDeleteTenant() { doReturn(folioExecutionContext).when(executionContextBuilder).buildContext(anyString()); mockOkapiHeaders(); - tenantService.delete(consortiumId, TENANT_ID); + tenantManager.delete(consortiumId, TENANT_ID); // Assert verify(consortiumService).checkConsortiumExistsOrThrow(consortiumId); @@ -305,7 +314,7 @@ void testDeleteNonexistentTenant() { // Call the method assertThrows(ResourceNotFoundException.class, () -> - tenantService.delete(consortiumId, tenantId)); + tenantManager.delete(consortiumId, tenantId)); } @Test @@ -321,7 +330,7 @@ void shouldThrowErrorWhenDeletingCentralTenant() { // Assert assertThrows(java.lang.IllegalArgumentException.class, () -> - tenantService.delete(consortiumId, TENANT_ID)); + tenantManager.delete(consortiumId, TENANT_ID)); verify(consortiumService).checkConsortiumExistsOrThrow(consortiumId); verify(tenantRepository).findById(TENANT_ID); @@ -335,26 +344,21 @@ void shouldThrowExceptionWhileSavingLocalTenantWithoutAdminUserId() { TenantDetailsEntity tenantDetailsEntity = createTenantDetailsEntity("TestID", "TestName1"); Tenant tenant = createTenant("TestID", "TestName2"); - when(tenantRepository.existsById(any())).thenReturn(false); when(tenantDetailsRepository.save(any(TenantDetailsEntity.class))).thenReturn(tenantDetailsEntity); - when(conversionService.convert(tenantDetailsEntity, Tenant.class)).thenReturn(tenant); assertThrows(java.lang.IllegalArgumentException.class, () -> - tenantService.save(UUID.fromString(CONSORTIUM_ID), null, tenant)); + tenantManager.save(CONSORTIUM_ID, null, tenant)); } @Test void shouldThrowExceptionWhileSavingWithDuplicateCodeOrName() { - TenantEntity tenantEntity1 = createTenantEntity("TestID", "TestName1"); Tenant tenant = createTenant("TestID", "TestName2"); - when(tenantRepository.existsById(any())).thenReturn(false); - when(tenantRepository.save(any(TenantEntity.class))).thenReturn(tenantEntity1); when(tenantRepository.existsByCodeForOtherTenant(anyString(), anyString())).thenReturn(true); - when(conversionService.convert(tenantEntity1, Tenant.class)).thenReturn(tenant); + var adminUserId = UUID.randomUUID(); assertThrows(ResourceAlreadyExistException.class, () -> - tenantService.save(UUID.fromString(CONSORTIUM_ID), UUID.randomUUID(), tenant)); + tenantManager.save(CONSORTIUM_ID, adminUserId, tenant)); } @Test @@ -364,8 +368,9 @@ void shouldThrowExceptionWhileUpdateTenant() { when(tenantRepository.findById(tenant.getId() + "1234")).thenReturn(Optional.of(existingTenant)); + var tenantId = tenant.getId() + "1234"; assertThrows(java.lang.IllegalArgumentException.class, () -> - tenantService.update(UUID.fromString(CONSORTIUM_ID), tenant.getId() + "1234", tenant)); + tenantManager.update(CONSORTIUM_ID, tenantId, tenant)); } @Test @@ -375,8 +380,9 @@ void shouldThrowNotFoundExceptionWhileUpdateTenant() { when(conversionService.convert(tenantEntity1, Tenant.class)).thenReturn(tenant); + var tenantId = tenant.getId() + "1234"; assertThrows(ResourceNotFoundException.class, () -> - tenantService.update(UUID.fromString(CONSORTIUM_ID), tenant.getId() + "1234", tenant)); + tenantManager.update(CONSORTIUM_ID, tenantId, tenant)); } @Test @@ -387,7 +393,7 @@ void shouldNotSaveExistingTenant() { when(tenantRepository.findById(tenant.getId())).thenReturn(Optional.of(existedTenant)); assertThrows(ResourceAlreadyExistException.class, - () -> tenantService.save(UUID.fromString(CONSORTIUM_ID), null, tenant)); + () -> tenantManager.save(CONSORTIUM_ID, null, tenant)); } @Test @@ -398,7 +404,7 @@ void shouldNotSaveDuplicateCentralTenant() { mockOkapiHeaders(); assertThrows(ResourceAlreadyExistException.class, () -> - tenantService.save(UUID.fromString(CONSORTIUM_ID), null, tenant)); + tenantManager.save(CONSORTIUM_ID, null, tenant)); } @Test @@ -453,7 +459,8 @@ void shouldThrowExceptionWhileAddingTenant_customFieldCreationError() { }); doThrow(new RuntimeException("Error")).when(customFieldService).createCustomField(ORIGINAL_TENANT_ID_CUSTOM_FIELD); - assertThrows(RuntimeException.class, () -> tenantService.save(consortiumId, UUID.fromString(adminUser.getId()), tenant)); + var adminUserId = UUID.fromString(adminUser.getId()); + assertThrows(RuntimeException.class, () -> tenantManager.save(consortiumId, adminUserId, tenant)); } private void mockOkapiHeaders() { @@ -461,4 +468,62 @@ private void mockOkapiHeaders() { Map> okapiHeaders = createOkapiHeaders(); when(folioExecutionContext.getOkapiHeaders()).thenReturn(okapiHeaders); } + + @Test + void testAddNewTenantWithExistentUserTenant() { + Tenant tenant = createTenant(TENANT_ID); + TenantDetailsEntity tenantDetailsEntity = createTenantDetailsEntity(tenant.getId(), tenant.getName()); + TenantEntity centralTenant = createTenantEntity(CENTRAL_TENANT_ID); + User adminUser = createUser("diku_admin"); + + when(tenantRepository.findById(tenant.getId())).thenReturn(Optional.empty()); + when(tenantRepository.findCentralTenant()).thenReturn(Optional.of(centralTenant)); + when(tenantDetailsRepository.save(any(TenantDetailsEntity.class))).thenReturn(tenantDetailsEntity); + when(conversionService.convert(tenantDetailsEntity, Tenant.class)).thenReturn(tenant); + when(userService.prepareShadowUser(any(UUID.class), anyString())).thenReturn(adminUser); + when(userTenantRepository.save(any(UserTenantEntity.class))).thenReturn(new UserTenantEntity()); + + doNothing().when(consortiaConfigurationService).createConfigurationIfNeeded(TENANT_ID); + when(userTenantsClient.getUserTenants()).thenReturn(new UserTenantCollection(List.of(), 1)); + + var tenant1 = tenantManager.save(CONSORTIUM_ID, UUID.fromString(adminUser.getId()), tenant); + + verify(userService, times(1)).prepareShadowUser(UUID.fromString(adminUser.getId()), "diku"); + verify(userTenantRepository, times(1)).save(any()); + verify(consortiaConfigurationService).createConfigurationIfNeeded(any()); + verify(lockService).lockTenantSetupWithinTransaction(); + verify(userTenantsClient, never()).postUserTenant(any()); + verify(userService, never()).getById(any()); + verify(userService, never()).createUser(any()); + verify(capabilitiesUserService, never()).createWithPermissionSetsFromFile(any(), any()); + verify(customFieldService, never()).createCustomField(any()); + + assertEquals(tenant, tenant1); + } + + @Test + void testReAddSoftDeletedTenantWithExistentUserTenant() { + Tenant tenant = createTenant(TENANT_ID); + TenantEntity tenantEntity = createTenantEntity(TENANT_ID); + tenantEntity.setIsDeleted(true); + TenantDetailsEntity tenantDetailsEntity = createTenantDetailsEntity(tenant.getId(), tenant.getName()); + TenantEntity centralTenant = createTenantEntity(CENTRAL_TENANT_ID); + User adminUser = createUser("diku_admin"); + + when(tenantRepository.findById(tenant.getId())).thenReturn(Optional.of(tenantEntity)); + when(tenantRepository.findCentralTenant()).thenReturn(Optional.of(centralTenant)); + when(tenantDetailsRepository.save(any(TenantDetailsEntity.class))).thenReturn(tenantDetailsEntity); + when(conversionService.convert(tenantDetailsEntity, Tenant.class)).thenReturn(tenant); + when(executionContextBuilder.buildContext(anyString())).thenReturn(folioExecutionContext); + + when(userTenantsClient.getUserTenants()).thenReturn(new UserTenantCollection(List.of(), 1)); + + var tenant1 = tenantManager.save(CONSORTIUM_ID, UUID.fromString(adminUser.getId()), tenant); + + verify(userTenantsClient, never()).postUserTenant(any()); + verify(customFieldService, never()).createCustomField(any()); + + assertEquals(tenant, tenant1); + } + } diff --git a/src/test/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImplTest.java b/src/test/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImplTest.java index 9762b8dd..2fb4653b 100644 --- a/src/test/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImplTest.java +++ b/src/test/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImplTest.java @@ -1,19 +1,20 @@ package org.folio.consortia.service.impl; import static org.folio.consortia.support.EntityUtils.createTenantEntity; +import static org.folio.consortia.support.EntityUtils.getFolioExecutionContext; import static org.folio.consortia.utils.InputOutputTestUtils.getMockDataAsString; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import org.folio.consortia.client.SyncPrimaryAffiliationClient; import org.folio.consortia.domain.entity.TenantEntity; import org.folio.consortia.domain.entity.UserTenantEntity; import org.folio.consortia.repository.TenantRepository; @@ -33,13 +34,19 @@ import org.folio.consortia.domain.dto.TenantDetails.SetupStatusEnum; import org.folio.consortia.domain.dto.User; import org.folio.consortia.domain.dto.UserCollection; +import org.folio.spring.FolioExecutionContext; +import org.folio.spring.data.OffsetRequest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; +import org.junit.jupiter.api.function.Executable; import org.mockito.Mock; +import org.mockito.Spy; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.data.domain.PageImpl; @@ -54,28 +61,33 @@ class SyncPrimaryAffiliationServiceImplTest { private static final String CQL_GET_USERS = "(cql.allRecords=1 NOT type=\"patron\" NOT type=\"dcb\" NOT type=\"shadow\" NOT type=\"system\")"; - @InjectMocks - SyncPrimaryAffiliationServiceImpl syncPrimaryAffiliationService; @Mock - private TenantService tenantService; - @Mock - private PrimaryAffiliationService primaryAffiliationService; + private TenantRepository tenantRepository; @Mock private UserTenantRepository userTenantRepository; @Mock - private ConsortiaConfigurationService consortiaConfigurationService; + private TenantService tenantService; @Mock - private SyncPrimaryAffiliationClient syncClient; - + private UserService userService; @Mock - UserService userService; + private PrimaryAffiliationService primaryAffiliationService; @Mock - TenantRepository tenantRepository; + private ConsortiaConfigurationService consortiaConfigurationService; @Mock private LockService lockService; - - protected static final String TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkaWt1X2FkbWluIiwidXNlcl9pZCI6IjFkM2I1OGNiLTA3YjUtNWZjZC04YTJhLTNjZTA2YTBlYjkwZiIsImlhdCI6MTYxNjQyMDM5MywidGVuYW50IjoiZGlrdSJ9.2nvEYQBbJP1PewEgxixBWLHSX_eELiBEBpjufWiJZRs"; - protected static final String TENANT = "diku"; + @Spy + private FolioExecutionContext folioExecutionContext = getFolioExecutionContext(); + @Spy + private AsyncTaskExecutor asyncTaskExecutor = new SimpleAsyncTaskExecutor(); + + private SyncPrimaryAffiliationServiceImpl syncPrimaryAffiliationService; + + @BeforeEach + void setUp() { + syncPrimaryAffiliationService = spy(new SyncPrimaryAffiliationServiceImpl(userService, tenantService, userTenantRepository, + lockService, primaryAffiliationService, folioExecutionContext, asyncTaskExecutor)); + syncPrimaryAffiliationService.setSyncPrimaryAffiliationService(syncPrimaryAffiliationService); + } @Test void createPrimaryUserAffiliationsWhenCentralTenantSaving() throws JsonProcessingException { @@ -102,7 +114,7 @@ void createPrimaryUserAffiliationsWhenCentralTenantSaving() throws JsonProcessin when(userService.getUsersByQuery(eq(CQL_GET_USERS), anyInt(), anyInt())).thenReturn(userCollection); when(consortiaConfigurationService.getCentralTenantId(anyString())).thenReturn(tenantId); - syncPrimaryAffiliationService.createPrimaryUserAffiliations(consortiumId, centralTenantId, spab); + syncPrimaryAffiliationService.createPrimaryUserAffiliationsInternal(consortiumId, centralTenantId, spab); verify(primaryAffiliationService).createPrimaryAffiliationInNewTransaction(any(), anyString(), any(), any()); verify(tenantService).updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.COMPLETED); @@ -133,7 +145,7 @@ void createPrimaryUserAffiliationsWhenLocalTenantSaving() throws JsonProcessingE when(userService.getUsersByQuery(eq(CQL_GET_USERS), anyInt(), anyInt())).thenReturn(userCollection); when(consortiaConfigurationService.getCentralTenantId(anyString())).thenReturn(centralTenantId); - syncPrimaryAffiliationService.createPrimaryUserAffiliations(consortiumId, centralTenantId, spab); + syncPrimaryAffiliationService.createPrimaryUserAffiliationsInternal(consortiumId, centralTenantId, spab); verify(primaryAffiliationService).createPrimaryAffiliationInNewTransaction(any(), anyString(), any(), any()); verify(tenantService).updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.COMPLETED); @@ -153,37 +165,18 @@ void syncPrimaryUserAffiliationsWhenTenantSaving() throws JsonProcessingExceptio var spab = getSyncBody(tenantId); + doNothing().when(tenantService).updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.COMPLETED); // stub collection of 2 users when(userService.getUsersByQuery(eq(CQL_GET_USERS), anyInt(), anyInt())).thenReturn(userCollection); + // stub userTenantRepository to return page with 1 element for each user to skip affiliation creation + userCollection.forEach(user -> + when(userTenantRepository.findAnyByUserId(UUID.fromString(user.getId()), OffsetRequest.of(0, 1))) + .thenReturn(new PageImpl<>(Collections.singletonList(new UserTenantEntity())))); - syncPrimaryAffiliationService.syncPrimaryAffiliations(consortiumId, tenantId, centralTenantId); - - verify(syncClient, timeout(2000)).savePrimaryAffiliations(spab, String.valueOf(consortiumId), tenantId, centralTenantId); - verify(tenantService, never()).updateTenantSetupStatus(any(), any(), any()); - } - - @Test - void syncPrimaryAffiliationsException() throws JsonProcessingException { - var consortiumId = UUID.randomUUID(); - var tenantId = "ABC1"; - var centralTenantId = "diku"; - - var userCollectionString = getMockDataAsString("mockdata/user_collection.json"); - List userCollection = new ObjectMapper().readValue(userCollectionString, UserCollection.class).getUsers(); - - var spab = getSyncBody(tenantId); - - // stub collection of 2 users - when(userService.getUsersByQuery(eq(CQL_GET_USERS), anyInt(), anyInt())).thenReturn(userCollection); - when(syncClient.savePrimaryAffiliations(spab, String.valueOf(consortiumId), tenantId, centralTenantId)) - .thenThrow(FeignException.FeignClientException.class); + syncPrimaryAffiliationService.syncPrimaryAffiliationsInternal(consortiumId, tenantId, centralTenantId); - try { - syncPrimaryAffiliationService.syncPrimaryAffiliations(consortiumId, tenantId, centralTenantId); - fail("Expected exception was not thrown"); - } catch (FeignException e) { - verify(tenantService).updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); - } + verify(syncPrimaryAffiliationService, timeout(2000)).createPrimaryUserAffiliationsInternal(consortiumId, centralTenantId, spab); + verify(tenantService).updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.COMPLETED); } @Test @@ -195,7 +188,7 @@ void syncPrimaryAffiliationsGetUsersException() { when(userService.getUsersByQuery(eq(CQL_GET_USERS), anyInt(), anyInt())) .thenThrow(FeignException.FeignClientException.class); - syncPrimaryAffiliationService.syncPrimaryAffiliations(consortiumId, tenantId, centralTenantId); + syncPrimaryAffiliationService.syncPrimaryAffiliationsInternal(consortiumId, tenantId, centralTenantId); verify(tenantService).updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); } @@ -214,14 +207,12 @@ void createPrimaryAffiliationsException() { when(tenantService.getByTenantId(anyString())).thenThrow(DataAccessResourceFailureException.class); - try { - syncPrimaryAffiliationService.createPrimaryUserAffiliations(consortiumId, centralTenantId, spab); - fail("Expected exception was not thrown"); - } catch (DataAccessResourceFailureException e) { - verifyNoInteractions(primaryAffiliationService); - verify(tenantService).updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); - verify(lockService).lockTenantSetupWithinTransaction(); - } + Executable call = () -> syncPrimaryAffiliationService.createPrimaryUserAffiliationsInternal(consortiumId, centralTenantId, spab); + + assertThrows(DataAccessResourceFailureException.class, call); + verifyNoInteractions(primaryAffiliationService); + verify(tenantService).updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.FAILED); + verify(lockService).lockTenantSetupWithinTransaction(); } @Test @@ -242,7 +233,8 @@ void createPrimaryAffiliationsPartialFailure() { when(tenantService.getByTenantId(anyString())).thenReturn(tenantEntity1) .thenThrow(DataAccessResourceFailureException.class); - syncPrimaryAffiliationService.createPrimaryUserAffiliations(consortiumId, centralTenantId, spab); + syncPrimaryAffiliationService.createPrimaryUserAffiliationsInternal(consortiumId, centralTenantId, spab); + verifyNoInteractions(primaryAffiliationService); verify(tenantService).updateTenantSetupStatus(tenantId, centralTenantId, SetupStatusEnum.COMPLETED_WITH_ERRORS); verify(lockService).lockTenantSetupWithinTransaction(); diff --git a/src/test/java/org/folio/consortia/support/EntityUtils.java b/src/test/java/org/folio/consortia/support/EntityUtils.java index 2866a4d6..7aaf5241 100644 --- a/src/test/java/org/folio/consortia/support/EntityUtils.java +++ b/src/test/java/org/folio/consortia/support/EntityUtils.java @@ -28,6 +28,9 @@ import org.folio.consortia.domain.entity.TenantDetailsEntity; import org.folio.consortia.domain.entity.TenantEntity; import org.folio.consortia.domain.entity.UserTenantEntity; +import org.folio.spring.DefaultFolioExecutionContext; +import org.folio.spring.FolioExecutionContext; +import org.folio.spring.FolioModuleMetadata; import org.folio.spring.integration.XOkapiHeaders; import org.springframework.http.HttpMethod; import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; @@ -446,4 +449,16 @@ public static Map> createOkapiHeaders() { map.put(XOkapiHeaders.USER_ID, List.of(UUID.randomUUID().toString())); return map; } + + public static FolioExecutionContext getFolioExecutionContext() { + return new DefaultFolioExecutionContext(new FolioModuleMetadata() { + public String getModuleName() { + return "mod-consortia-keycloak"; + } + + public String getDBSchemaName(String tenantId) { + return "mod_consortia_keycloak"; + } + }, createOkapiHeaders()); + } }