diff --git a/pom.xml b/pom.xml index e217be5f..0e7b3c6c 100644 --- a/pom.xml +++ b/pom.xml @@ -68,11 +68,11 @@ 1.4.0 - 2.27.2 - 1.19.7 + 3.0.1 + 1.19.3 2.2 - 1.19.7 - 3.2.0 + 1.19.3 + 3.3.1 diff --git a/src/main/java/org/folio/consortia/client/UserTenantsClient.java b/src/main/java/org/folio/consortia/client/UserTenantsClient.java index fb9b8a47..badc7665 100644 --- a/src/main/java/org/folio/consortia/client/UserTenantsClient.java +++ b/src/main/java/org/folio/consortia/client/UserTenantsClient.java @@ -4,6 +4,7 @@ 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.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -12,4 +13,7 @@ public interface UserTenantsClient { @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) void postUserTenant(@RequestBody UserTenant userTenant); + + @DeleteMapping + void deleteUserTenants(); } diff --git a/src/main/java/org/folio/consortia/config/AppConfig.java b/src/main/java/org/folio/consortia/config/AppConfig.java index 50cfd289..5065df48 100644 --- a/src/main/java/org/folio/consortia/config/AppConfig.java +++ b/src/main/java/org/folio/consortia/config/AppConfig.java @@ -48,7 +48,7 @@ public ObjectMapper objectMapper() { objectMapper.findAndRegisterModules() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + .setSerializationInclusion(JsonInclude.Include.NON_NULL); return objectMapper; } diff --git a/src/main/java/org/folio/consortia/domain/converter/TenantDetailsEntityToTenantConverter.java b/src/main/java/org/folio/consortia/domain/converter/TenantDetailsEntityToTenantConverter.java index 1738bff6..4a207caf 100644 --- a/src/main/java/org/folio/consortia/domain/converter/TenantDetailsEntityToTenantConverter.java +++ b/src/main/java/org/folio/consortia/domain/converter/TenantDetailsEntityToTenantConverter.java @@ -14,6 +14,7 @@ public Tenant convert(TenantDetailsEntity source) { tenant.setCode(source.getCode()); tenant.setName(source.getName()); tenant.setIsCentral(source.getIsCentral()); + tenant.setIsDeleted(source.getIsDeleted()); return tenant; } } diff --git a/src/main/java/org/folio/consortia/domain/converter/TenantDetailsEntityToTenantDetailsConverter.java b/src/main/java/org/folio/consortia/domain/converter/TenantDetailsEntityToTenantDetailsConverter.java index 243de6ed..3c8c3b10 100644 --- a/src/main/java/org/folio/consortia/domain/converter/TenantDetailsEntityToTenantDetailsConverter.java +++ b/src/main/java/org/folio/consortia/domain/converter/TenantDetailsEntityToTenantDetailsConverter.java @@ -14,6 +14,7 @@ public TenantDetails convert(TenantDetailsEntity source) { .code(source.getCode()) .name(source.getName()) .isCentral(source.getIsCentral()) + .isDeleted(source.getIsDeleted()) .setupStatus(source.getSetupStatus()); } } diff --git a/src/main/java/org/folio/consortia/domain/converter/TenantEntityToTenantConverter.java b/src/main/java/org/folio/consortia/domain/converter/TenantEntityToTenantConverter.java index a1f6dc1e..8bc238ae 100644 --- a/src/main/java/org/folio/consortia/domain/converter/TenantEntityToTenantConverter.java +++ b/src/main/java/org/folio/consortia/domain/converter/TenantEntityToTenantConverter.java @@ -15,6 +15,7 @@ public Tenant convert(TenantEntity source) { tenant.setCode(source.getCode()); tenant.setName(source.getName()); tenant.setIsCentral(source.getIsCentral()); + tenant.setIsDeleted(source.getIsDeleted()); return tenant; } } diff --git a/src/main/java/org/folio/consortia/domain/entity/AbstractTenantEntity.java b/src/main/java/org/folio/consortia/domain/entity/AbstractTenantEntity.java index 0383473d..06502e3c 100644 --- a/src/main/java/org/folio/consortia/domain/entity/AbstractTenantEntity.java +++ b/src/main/java/org/folio/consortia/domain/entity/AbstractTenantEntity.java @@ -23,16 +23,19 @@ public abstract class AbstractTenantEntity extends AuditableEntity { private String name; private UUID consortiumId; private Boolean isCentral; + private Boolean isDeleted; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AbstractTenantEntity that)) return false; - return Objects.equals(id, that.id) && Objects.equals(code, that.code) && Objects.equals(name, that.name) && Objects.equals(consortiumId, that.consortiumId) && Objects.equals(isCentral, that.isCentral); + return Objects.equals(id, that.id) && Objects.equals(code, that.code) && + Objects.equals(name, that.name) && Objects.equals(consortiumId, that.consortiumId) && + Objects.equals(isCentral, that.isCentral) && Objects.equals(isDeleted, that.isDeleted); } @Override public int hashCode() { - return Objects.hash(id, code, name, consortiumId, isCentral); + return Objects.hash(id, code, name, consortiumId, isCentral, isDeleted); } } diff --git a/src/main/java/org/folio/consortia/repository/SharingInstanceRepository.java b/src/main/java/org/folio/consortia/repository/SharingInstanceRepository.java index 42682a09..7a8b7264 100644 --- a/src/main/java/org/folio/consortia/repository/SharingInstanceRepository.java +++ b/src/main/java/org/folio/consortia/repository/SharingInstanceRepository.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import org.apache.commons.lang3.StringUtils; @@ -10,11 +11,15 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @Repository public interface SharingInstanceRepository extends JpaRepository, JpaSpecificationExecutor { + @Query("SELECT si FROM SharingInstanceEntity si WHERE si.instanceId = ?1 AND si.sourceTenantId= ?2 AND si.targetTenantId= ?3") + Optional findByInstanceAndTenantIds(UUID instanceIdentifier, String sourceTenantId, String targetTenantId); + interface Specifications { static Specification constructSpecification(UUID instanceIdentifier, String sourceTenantId, String targetTenantId, Status status) { diff --git a/src/main/java/org/folio/consortia/repository/TenantRepository.java b/src/main/java/org/folio/consortia/repository/TenantRepository.java index 819f5c72..69be4a3e 100644 --- a/src/main/java/org/folio/consortia/repository/TenantRepository.java +++ b/src/main/java/org/folio/consortia/repository/TenantRepository.java @@ -14,13 +14,21 @@ @Repository public interface TenantRepository extends JpaRepository { + @Query("SELECT t FROM TenantEntity t WHERE t.consortiumId = ?1 and t.isDeleted = FALSE") Page findByConsortiumId(UUID consortiumId, Pageable pageable); + + @Query("SELECT t FROM TenantEntity t WHERE t.consortiumId = ?1 and t.isDeleted = FALSE") List findByConsortiumId(UUID consortiumId); - @Query("SELECT t FROM TenantEntity t where t.isCentral = true") + @Query("SELECT t FROM TenantEntity t WHERE t.isCentral = true") Optional findCentralTenant(); boolean existsByIsCentralTrue(); - boolean existsByCode(String code); - boolean existsByName(String name); + @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); + + @Query("SELECT CASE WHEN COUNT(t) > 0 THEN TRUE ELSE FALSE END FROM TenantEntity t " + + "WHERE t.name = ?1 AND t.id != ?2") + boolean existsByNameForOtherTenant(String name, String tenantId); } diff --git a/src/main/java/org/folio/consortia/repository/UserTenantRepository.java b/src/main/java/org/folio/consortia/repository/UserTenantRepository.java index 29f7bb6d..85918d5d 100644 --- a/src/main/java/org/folio/consortia/repository/UserTenantRepository.java +++ b/src/main/java/org/folio/consortia/repository/UserTenantRepository.java @@ -14,16 +14,22 @@ @Repository public interface UserTenantRepository extends JpaRepository { + + @Query("SELECT ut FROM UserTenantEntity ut WHERE ut.tenant.isDeleted= FALSE") + Page getAll(Pageable pageable); + + @Query("SELECT ut FROM UserTenantEntity ut WHERE ut.userId= ?1 AND ut.tenant.isDeleted= FALSE") Page findByUserId(UUID userId, Pageable pageable); - @Query("SELECT ut FROM UserTenantEntity ut WHERE ut.username= ?1 AND ut.tenant.id= ?2") + @Query("SELECT ut FROM UserTenantEntity ut WHERE ut.userId= ?1") + Page findAnyByUserId(UUID userId, Pageable pageable); + + @Query("SELECT ut FROM UserTenantEntity ut WHERE ut.username= ?1 AND ut.tenant.id= ?2 AND ut.tenant.isDeleted= FALSE") Optional findByUsernameAndTenantId(String username, String tenantId); @Query("SELECT ut FROM UserTenantEntity ut WHERE ut.userId= ?1 AND ut.tenant.id= ?2") Optional findByUserIdAndTenantId(UUID userId, String tenantId); - boolean existsByTenantId(String tenantId); - @Query("SELECT ut FROM UserTenantEntity ut WHERE ut.userId= ?1 AND ut.isPrimary= true") Optional findByUserIdAndIsPrimaryTrue(UUID userId); diff --git a/src/main/java/org/folio/consortia/service/ConsortiaConfigurationService.java b/src/main/java/org/folio/consortia/service/ConsortiaConfigurationService.java index 1e42130d..ff8aefae 100644 --- a/src/main/java/org/folio/consortia/service/ConsortiaConfigurationService.java +++ b/src/main/java/org/folio/consortia/service/ConsortiaConfigurationService.java @@ -27,4 +27,9 @@ public interface ConsortiaConfigurationService { */ ConsortiaConfiguration createConfiguration(String centralTenantId); + /** + * Check if there is any central tenant + * @return boolean value based one whether central tenant configuration exists or not + */ + boolean isCentralTenantConfigurationExists(); } diff --git a/src/main/java/org/folio/consortia/service/TenantService.java b/src/main/java/org/folio/consortia/service/TenantService.java index fc106510..4a8c8ca4 100644 --- a/src/main/java/org/folio/consortia/service/TenantService.java +++ b/src/main/java/org/folio/consortia/service/TenantService.java @@ -31,6 +31,10 @@ public interface TenantService { /** * 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 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 bad638a7..b78e85f8 100644 --- a/src/main/java/org/folio/consortia/service/impl/ConsortiaConfigurationServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/ConsortiaConfigurationServiceImpl.java @@ -1,20 +1,19 @@ package org.folio.consortia.service.impl; -import org.folio.consortia.exception.ResourceAlreadyExistException; -import org.folio.consortia.exception.ResourceNotFoundException; -import org.folio.consortia.repository.ConsortiaConfigurationRepository; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.consortia.domain.dto.ConsortiaConfiguration; import org.folio.consortia.domain.entity.ConsortiaConfigurationEntity; +import org.folio.consortia.exception.ResourceAlreadyExistException; +import org.folio.consortia.exception.ResourceNotFoundException; +import org.folio.consortia.repository.ConsortiaConfigurationRepository; import org.folio.consortia.service.ConsortiaConfigurationService; import org.folio.consortia.utils.TenantContextUtils; import org.folio.spring.FolioExecutionContext; import org.springframework.core.convert.ConversionService; import org.springframework.stereotype.Service; -import java.util.List; - @Log4j2 @Service @RequiredArgsConstructor @@ -54,6 +53,10 @@ 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/ConsortiumServiceImpl.java b/src/main/java/org/folio/consortia/service/impl/ConsortiumServiceImpl.java index e07b7e17..bb1e4ac0 100644 --- a/src/main/java/org/folio/consortia/service/impl/ConsortiumServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/ConsortiumServiceImpl.java @@ -1,22 +1,21 @@ package org.folio.consortia.service.impl; -import org.folio.consortia.exception.ResourceAlreadyExistException; -import org.folio.consortia.exception.ResourceNotFoundException; -import org.folio.consortia.repository.ConsortiumRepository; +import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.consortia.domain.dto.Consortium; import org.folio.consortia.domain.dto.ConsortiumCollection; import org.folio.consortia.domain.entity.ConsortiumEntity; +import org.folio.consortia.exception.ResourceAlreadyExistException; +import org.folio.consortia.exception.ResourceNotFoundException; +import org.folio.consortia.repository.ConsortiumRepository; import org.folio.consortia.service.ConsortiumService; import org.folio.consortia.utils.HelperUtils; +import org.folio.spring.data.OffsetRequest; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; -import java.util.UUID; - @Service @Log4j2 @RequiredArgsConstructor @@ -57,7 +56,7 @@ public Consortium update(UUID consortiumId, Consortium consortiumDto) { public ConsortiumCollection getAll() { var result = new ConsortiumCollection(); - Page consortiaPage = repository.findAll(PageRequest.of(0, 1)); + Page consortiaPage = repository.findAll(OffsetRequest.of(0, 1)); result.setConsortia(consortiaPage.stream().map(o -> converter.convert(o, Consortium.class)).toList()); result.setTotalRecords((int) consortiaPage.getTotalElements()); return result; diff --git a/src/main/java/org/folio/consortia/service/impl/PublicationServiceImpl.java b/src/main/java/org/folio/consortia/service/impl/PublicationServiceImpl.java index 837cf843..b25aadd4 100644 --- a/src/main/java/org/folio/consortia/service/impl/PublicationServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/PublicationServiceImpl.java @@ -41,11 +41,11 @@ import org.folio.consortia.utils.TenantContextUtils; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; +import org.folio.spring.data.OffsetRequest; import org.folio.spring.scope.FolioExecutionContextSetter; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.task.TaskExecutor; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -95,7 +95,7 @@ public PublicationDetailsResponse getPublicationDetails(UUID consortiumId, UUID var publicationStatusEntity = publicationStatusRepository.findById(publicationId) .orElseThrow(() -> new ResourceNotFoundException(PUBLICATION_ID_FIELD, String.valueOf(publicationId))); - var ptrEntities = publicationTenantRequestRepository.findByPcStateId(publicationId, PageRequest.of(0, Integer.MAX_VALUE)); + var ptrEntities = publicationTenantRequestRepository.findByPcStateId(publicationId, OffsetRequest.of(0, Integer.MAX_VALUE)); log.info("getPublicationDetails:: Found {} of {} expected tenant request records", ptrEntities.getTotalElements(), publicationStatusEntity.getTotalRecords()); var errorList = buildErrorListFromPublicationTenantRequestEntities(ptrEntities); @@ -312,7 +312,7 @@ public PublicationResultCollection getPublicationResults(UUID consortiumId, UUID var publicationStatusEntity = publicationStatusRepository.findById(publicationId) .orElseThrow(() -> new ResourceNotFoundException(PUBLICATION_ID_FIELD, String.valueOf(publicationId))); - var ptrEntities = publicationTenantRequestRepository.findByPcStateId(publicationId, PageRequest.of(0, Integer.MAX_VALUE)); + var ptrEntities = publicationTenantRequestRepository.findByPcStateId(publicationId, OffsetRequest.of(0, Integer.MAX_VALUE)); log.info("getPublicationResults:: Found {} of {} expected tenant request records for publication {}", ptrEntities.getTotalElements(), publicationStatusEntity.getTotalRecords(), publicationId); var resultList = ptrEntities.stream() diff --git a/src/main/java/org/folio/consortia/service/impl/SharingInstanceServiceImpl.java b/src/main/java/org/folio/consortia/service/impl/SharingInstanceServiceImpl.java index e85d77b7..5828869f 100644 --- a/src/main/java/org/folio/consortia/service/impl/SharingInstanceServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/SharingInstanceServiceImpl.java @@ -1,11 +1,15 @@ package org.folio.consortia.service.impl; -import org.folio.consortia.exception.ResourceNotFoundException; -import org.folio.consortia.repository.SharingInstanceRepository; -import org.folio.consortia.repository.SharingInstanceRepository.Specifications; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import jakarta.transaction.Transactional; import java.util.Objects; import java.util.UUID; - +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.ObjectUtils; import org.folio.consortia.client.InventoryClient; import org.folio.consortia.config.kafka.KafkaService; @@ -13,6 +17,9 @@ import org.folio.consortia.domain.dto.SharingInstanceCollection; import org.folio.consortia.domain.dto.Status; import org.folio.consortia.domain.entity.SharingInstanceEntity; +import org.folio.consortia.exception.ResourceNotFoundException; +import org.folio.consortia.repository.SharingInstanceRepository; +import org.folio.consortia.repository.SharingInstanceRepository.Specifications; import org.folio.consortia.service.ConsortiumService; import org.folio.consortia.service.InventoryService; import org.folio.consortia.service.SharingInstanceService; @@ -21,21 +28,11 @@ import org.folio.consortia.utils.TenantContextUtils; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; +import org.folio.spring.data.OffsetRequest; import org.folio.spring.scope.FolioExecutionContextSetter; import org.springframework.core.convert.ConversionService; -import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; - -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.log4j.Log4j2; - @Service @Log4j2 @RequiredArgsConstructor @@ -76,7 +73,7 @@ public SharingInstance start(UUID consortiumId, SharingInstance sharingInstance) if (Objects.equals(centralTenantId, sourceTenantId)) { JsonNode inventoryInstance; - try (var context = new FolioExecutionContextSetter( + try (var ignored = new FolioExecutionContextSetter( TenantContextUtils.prepareContextForTenant(sourceTenantId, folioModuleMetadata, folioExecutionContext))) { inventoryInstance = inventoryService.getById(sharingInstance.getInstanceIdentifier()); } catch (Exception ex) { @@ -84,7 +81,7 @@ public SharingInstance start(UUID consortiumId, SharingInstance sharingInstance) return updateFieldsAndSaveInCaseOfException(sharingInstance, GET_INSTANCE_EXCEPTION_MSG, ex); } - try (var context = new FolioExecutionContextSetter( + try (var ignored = new FolioExecutionContextSetter( TenantContextUtils.prepareContextForTenant(targetTenantId, folioModuleMetadata, folioExecutionContext))) { String source = switch (inventoryInstance.get("source").asText().toLowerCase()) { case "folio" -> HelperUtils.CONSORTIUM_FOLIO_INSTANCE_SOURCE; @@ -105,21 +102,47 @@ public SharingInstance start(UUID consortiumId, SharingInstance sharingInstance) sharingInstance.setStatus(Status.IN_PROGRESS); } - SharingInstanceEntity savedSharingInstance = sharingInstanceRepository.save(toEntity(sharingInstance)); + + SharingInstanceEntity savedSharingInstance = saveSharingInstance(sharingInstance); log.info("start:: sharingInstance with id: {}, instanceId: {}, sourceTenantId: {}, targetTenantId: {} has been saved with status: {}", savedSharingInstance.getId(), savedSharingInstance.getInstanceId(), sourceTenantId, targetTenantId, savedSharingInstance.getStatus()); return converter.convert(savedSharingInstance, SharingInstance.class); } + /** + * This method save sharing instance record to database. + * Before to save sharing instance to db, previous attempt will be checked. + * If a matching previous attempt is found, the method updates it with the new attempt's error and status information. + * Otherwise, new record will be created. + * + * @param sharingInstance sharingInstanceDto + * @return saved sharing instance entity + */ + private SharingInstanceEntity saveSharingInstance(SharingInstance sharingInstance) { + var existingSharingInstanceOptional = sharingInstanceRepository.findByInstanceAndTenantIds( + sharingInstance.getInstanceIdentifier(), sharingInstance.getSourceTenantId(), sharingInstance.getTargetTenantId()); + + if (existingSharingInstanceOptional.isEmpty()) { + log.info("saveSharingInstance:: There is no existing record, so creating new record"); + return sharingInstanceRepository.save(toEntity(sharingInstance)); + } + + log.info("saveSharingInstance:: Existed sharingInstance is being updated with new attempt with status={}", sharingInstance.getStatus()); + var existingSharingInstance = existingSharingInstanceOptional.get(); + existingSharingInstance.setError(sharingInstance.getError()); + existingSharingInstance.setStatus(sharingInstance.getStatus()); + return sharingInstanceRepository.save(existingSharingInstance); + } + @Override public SharingInstanceCollection getSharingInstances(UUID consortiumId, UUID instanceIdentifier, String sourceTenantId, - String targetTenantId, Status status, Integer offset, Integer limit) { + String targetTenantId, Status status, Integer offset, Integer limit) { log.debug("getSharingInstances:: parameters consortiumId: {}, instanceIdentifier: {}, sourceTenantId: {}, targetTenantId: {}, status: {}.", consortiumId, instanceIdentifier, sourceTenantId, targetTenantId, status); consortiumService.checkConsortiumExistsOrThrow(consortiumId); var specification = Specifications.constructSpecification(instanceIdentifier, sourceTenantId, targetTenantId, status); - var sharingInstancePage = sharingInstanceRepository.findAll(specification, PageRequest.of(offset, limit)); + var sharingInstancePage = sharingInstanceRepository.findAll(specification, OffsetRequest.of(offset, limit)); var result = new SharingInstanceCollection(); result.setSharingInstances(sharingInstancePage.stream().map(o -> converter.convert(o, SharingInstance.class)).toList()); result.setTotalRecords((int) sharingInstancePage.getTotalElements()); @@ -182,6 +205,13 @@ private void checkTenantsExistAndContainCentralTenantOrThrow(String sourceTenant throw new IllegalArgumentException("Both 'sourceTenantId' and 'targetTenantId' cannot be member tenants."); } + private SharingInstance updateFieldsAndSaveInCaseOfException(SharingInstance sharingInstance, String message, Exception ex) { + sharingInstance.setStatus(Status.ERROR); + sharingInstance.setError(String.format(message, InventoryClient.getReason(ex))); + var savedSharingInstance = saveSharingInstance(sharingInstance); + return converter.convert(savedSharingInstance, SharingInstance.class); + } + private SharingInstanceEntity toEntity(SharingInstance dto) { SharingInstanceEntity entity = new SharingInstanceEntity(); entity.setId(UUID.randomUUID()); @@ -192,11 +222,4 @@ private SharingInstanceEntity toEntity(SharingInstance dto) { entity.setError(dto.getError()); return entity; } - - private SharingInstance updateFieldsAndSaveInCaseOfException(SharingInstance sharingInstance, String message, Exception ex) { - sharingInstance.setStatus(Status.ERROR); - sharingInstance.setError(String.format(message, InventoryClient.getReason(ex))); - var savedSharingInstance = sharingInstanceRepository.save(toEntity(sharingInstance)); - return converter.convert(savedSharingInstance, SharingInstance.class); - } } 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 e86dfc37..cb0a3d36 100644 --- a/src/main/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImpl.java @@ -1,11 +1,11 @@ package org.folio.consortia.service.impl; -import org.folio.consortia.repository.UserTenantRepository; import java.util.ArrayList; 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.lang3.StringUtils; import org.folio.consortia.client.SyncPrimaryAffiliationClient; @@ -17,19 +17,17 @@ import org.folio.consortia.domain.dto.User; import org.folio.consortia.domain.entity.TenantEntity; import org.folio.consortia.domain.entity.UserTenantEntity; +import org.folio.consortia.repository.UserTenantRepository; import org.folio.consortia.service.LockService; import org.folio.consortia.service.PrimaryAffiliationService; import org.folio.consortia.service.SyncPrimaryAffiliationService; import org.folio.consortia.service.TenantService; import org.folio.consortia.service.UserService; +import org.folio.spring.data.OffsetRequest; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; - @Service @Log4j2 @RequiredArgsConstructor @@ -118,7 +116,7 @@ private void createPrimaryUserAffiliations(UUID consortiumId, String centralTena var user = userList.get(idx); try { log.info("createPrimaryUserAffiliations:: Processing users: {} of {}", idx + 1, userList.size()); - Page userTenantPage = userTenantRepository.findByUserId(UUID.fromString(user.getId()), PageRequest.of(0, 1)); + Page userTenantPage = userTenantRepository.findAnyByUserId(UUID.fromString(user.getId()), OffsetRequest.of(0, 1)); if (userTenantPage.getTotalElements() > 0) { log.info("createPrimaryUserAffiliations:: Primary affiliation already exists for tenant/user: {}/{}", 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 8015c68f..24aa5815 100644 --- a/src/main/java/org/folio/consortia/service/impl/TenantServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/TenantServiceImpl.java @@ -1,5 +1,7 @@ package org.folio.consortia.service.impl; +import static org.folio.consortia.utils.HelperUtils.checkIdenticalOrThrow; + import java.util.List; import java.util.Objects; import java.util.UUID; @@ -7,6 +9,7 @@ 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; @@ -31,15 +34,14 @@ import org.folio.consortia.service.PermissionUserService; import org.folio.consortia.service.TenantService; import org.folio.consortia.service.UserService; -import org.folio.consortia.utils.HelperUtils; 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.beans.factory.annotation.Value; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -52,8 +54,7 @@ public class TenantServiceImpl implements TenantService { private static final String SHADOW_ADMIN_PERMISSION_FILE_PATH = "permissions/admin-user-permissions.csv"; private static final String SHADOW_SYSTEM_USER_PERMISSION_FILE_PATH = "permissions/system-user-permissions.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 TENANT_HAS_ACTIVE_USER_ASSOCIATIONS_ERROR_MSG = "Cannot delete tenant with ID {tenantId} because it has an association with a user. " - + "Please remove the user association before attempting to delete the tenant."; + private static final String DUMMY_USERNAME = "dummy_user"; @Value("${folio.system-user.username}") private String systemUserUsername; @@ -67,7 +68,7 @@ public class TenantServiceImpl implements TenantService { private final ConsortiaConfigurationClient configurationClient; private final PermissionUserService permissionUserService; private final UserService userService; - private final SystemUserScopedExecutionService systemUserScopedExecutionService; + private final ExecutionContextBuilder contextBuilder; private final UserTenantsClient userTenantsClient; private final SyncPrimaryAffiliationClient syncPrimaryAffiliationClient; private final CleanupService cleanupService; @@ -77,7 +78,7 @@ public class TenantServiceImpl implements TenantService { public TenantCollection get(UUID consortiumId, Integer offset, Integer limit) { TenantCollection result = new TenantCollection(); consortiumService.checkConsortiumExistsOrThrow(consortiumId); - Page page = tenantRepository.findByConsortiumId(consortiumId, PageRequest.of(offset, limit)); + Page page = tenantRepository.findByConsortiumId(consortiumId, OffsetRequest.of(offset, limit)); result.setTenants(page.map(o -> converter.convert(o, Tenant.class)).getContent()); result.setTotalRecords((int) page.getTotalElements()); return result; @@ -87,8 +88,7 @@ public TenantCollection get(UUID consortiumId, Integer offset, Integer limit) { public TenantCollection getAll(UUID consortiumId) { TenantCollection result = new TenantCollection(); List list = tenantRepository.findByConsortiumId(consortiumId) - .stream() - .map(o -> converter.convert(o, Tenant.class)).toList(); + .stream().map(o -> converter.convert(o, Tenant.class)).toList(); result.setTenants(list); result.setTotalRecords(list.size()); return result; @@ -118,18 +118,42 @@ public TenantEntity getByTenantId(String tenantId) { @Override @Transactional public Tenant save(UUID consortiumId, UUID adminUserId, Tenant tenantDto) { - log.info("save:: Trying to save a tenant by consortiumId '{}', tenant object with id '{}' and isCentral={}", consortiumId, - tenantDto.getId(), tenantDto.getIsCentral()); + log.info("save:: Trying to save a tenant with id={}, consortiumId={} and isCentral={}", tenantDto.getId(), + consortiumId, tenantDto.getIsCentral()); + validateConsortiumAndTenantForSaveOperation(consortiumId, tenantDto); + validateCodeAndNameUniqueness(tenantDto); - // validation part - checkTenantNotExistsAndConsortiumExistsOrThrow(consortiumId, tenantDto.getId()); - checkCodeAndNameUniqueness(tenantDto); - if (tenantDto.getIsCentral()) { - checkCentralTenantExistsOrThrow(); + var existingTenant = tenantRepository.findById(tenantDto.getId()); + + // checked whether tenant exists or not. + return existingTenant.isPresent() ? reAddSoftDeletedTenant(consortiumId, existingTenant.get(), tenantDto) + : addNewTenant(consortiumId, tenantDto, adminUserId); + } + + 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; + } + + private Tenant addNewTenant(UUID consortiumId, Tenant tenantDto, UUID adminUserId) { + log.info("addNewTenant:: Creating new tenant with id={}, consortiumId={}, and adminUserId={}", + tenantDto.getId(), consortiumId, adminUserId); - // save tenant to db lockService.lockTenantSetupWithinTransaction(); + tenantDto.setIsDeleted(false); Tenant savedTenant = saveTenant(consortiumId, tenantDto, SetupStatusEnum.IN_PROGRESS); // save admin user tenant association for non-central tenant @@ -145,7 +169,7 @@ public Tenant save(UUID consortiumId, UUID adminUserId, Tenant tenantDto) { userTenantRepository.save(createUserTenantEntity(consortiumId, shadowAdminUser, tenantDto)); // creating shadow user of consortia system user of central tenant with same permissions. var centralSystemUser = userService.getByUsername(systemUserUsername) - .orElseThrow(() -> new ResourceNotFoundException("systemUserUsername", systemUserUsername)); + .orElseThrow(() -> new ResourceNotFoundException("systemUserUsername", systemUserUsername)); shadowSystemUser = userService.prepareShadowUser(UUID.fromString(centralSystemUser.getId()), folioExecutionContext.getTenantId()); userTenantRepository.save(createUserTenantEntity(consortiumId, shadowSystemUser, tenantDto)); } @@ -156,7 +180,7 @@ public Tenant save(UUID consortiumId, UUID adminUserId, Tenant tenantDto) { var allHeaders = new CaseInsensitiveMap<>(folioExecutionContext.getOkapiHeaders()); allHeaders.put("x-okapi-tenant", List.of(tenantDto.getId())); - try (var fex = new FolioExecutionContextSetter(folioExecutionContext.getFolioModuleMetadata(), allHeaders)) { + try (var ignored = new FolioExecutionContextSetter(folioExecutionContext.getFolioModuleMetadata(), allHeaders)) { configurationClient.saveConfiguration(createConsortiaConfigurationBody(centralTenantId)); if (!tenantDto.getIsCentral()) { createUserTenantWithDummyUser(tenantDto.getId(), centralTenantId, consortiumId); @@ -174,16 +198,20 @@ public Tenant save(UUID consortiumId, UUID adminUserId, Tenant tenantDto) { @Override @Transactional public Tenant update(UUID consortiumId, String tenantId, Tenant tenantDto) { - consortiumService.checkConsortiumExistsOrThrow(consortiumId); - checkTenantExistsOrThrow(tenantId); - HelperUtils.checkIdenticalOrThrow(tenantId, tenantDto.getId(), TENANTS_IDS_NOT_MATCHED_ERROR_MSG); + 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); } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateTenantSetupStatus(String tenantId, String centralTenantId, SetupStatusEnum setupStatus) { - try (var ctx = new FolioExecutionContextSetter(TenantContextUtils.prepareContextForTenant(centralTenantId, + try (var ignored = new FolioExecutionContextSetter(TenantContextUtils.prepareContextForTenant(centralTenantId, folioExecutionContext.getFolioModuleMetadata(), folioExecutionContext))) { tenantDetailsRepository.setSetupStatusByTenantId(setupStatus, tenantId); log.info("updateTenantSetupStatus:: tenant id={} status updated to {}", tenantId, setupStatus); @@ -194,13 +222,23 @@ public void updateTenantSetupStatus(String tenantId, String centralTenantId, Set @Transactional public void delete(UUID consortiumId, String tenantId) { consortiumService.checkConsortiumExistsOrThrow(consortiumId); - checkTenantExistsOrThrow(tenantId); - if (userTenantRepository.existsByTenantId(tenantId)) { - throw new IllegalArgumentException(TENANT_HAS_ACTIVE_USER_ASSOCIATIONS_ERROR_MSG); + var tenant = tenantRepository.findById(tenantId); + + if (tenant.isEmpty()) { + throw new ResourceNotFoundException("id", tenantId); } + + 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.deleteById(tenantId); + tenantRepository.save(softDeletedTenant); + + try (var ignored = new FolioExecutionContextSetter(contextBuilder.buildContext(tenantId))) { + userTenantsClient.deleteUserTenants(); + } } private Tenant saveTenant(UUID consortiumId, Tenant tenantDto, SetupStatusEnum setupStatus) { @@ -213,22 +251,21 @@ private Tenant saveTenant(UUID consortiumId, Tenant tenantDto, SetupStatusEnum s } private Tenant updateTenant(UUID consortiumId, Tenant tenantDto) { - log.debug("updateTenant:: Trying to update tenant with consoritumId={} and tenant with id={}", consortiumId, 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 - */ + /** + * 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()); @@ -242,22 +279,6 @@ private void createUserTenantWithDummyUser(String tenantId, String centralTenant userTenantsClient.postUserTenant(userTenant); } - private void checkTenantNotExistsAndConsortiumExistsOrThrow(UUID consortiumId, String tenantId) { - consortiumService.checkConsortiumExistsOrThrow(consortiumId); - if (tenantRepository.existsById(tenantId)) { - throw new ResourceAlreadyExistException("id", tenantId); - } - } - - private void checkCodeAndNameUniqueness(Tenant tenant) { - if (tenantRepository.existsByName(tenant.getName())) { - throw new ResourceAlreadyExistException("name", tenant.getName()); - } - if (tenantRepository.existsByCode(tenant.getCode())) { - throw new ResourceAlreadyExistException("code", tenant.getCode()); - } - } - @Override public void checkTenantExistsOrThrow(String tenantId) { if (!tenantRepository.existsById(tenantId)) { @@ -281,12 +302,45 @@ public void checkTenantsAndConsortiumExistsOrThrow(UUID consortiumId, List @@ -64,8 +63,8 @@ public class UserTenantServiceImpl implements UserTenantService { public UserTenantCollection get(UUID consortiumId, Integer offset, Integer limit) { consortiumService.checkConsortiumExistsOrThrow(consortiumId); var result = new UserTenantCollection(); - Page userTenantPage = userTenantRepository.findAll(PageRequest.of(offset, limit)); - result.setUserTenants(userTenantPage.stream().map(o -> converter.convert(o, UserTenant.class)).toList()); + Page userTenantPage = userTenantRepository.getAll(OffsetRequest.of(offset, limit)); + result.setUserTenants(userTenantPage.map(o -> converter.convert(o, UserTenant.class)).getContent()); result.setTotalRecords((int) userTenantPage.getTotalElements()); return result; } @@ -95,7 +94,7 @@ public UserTenantCollection getByUsernameAndTenantId(UUID consortiumId, String u public UserTenantCollection getByUserId(UUID consortiumId, UUID userId, Integer offset, Integer limit) { consortiumService.checkConsortiumExistsOrThrow(consortiumId); var result = new UserTenantCollection(); - Page userTenantPage = userTenantRepository.findByUserId(userId, PageRequest.of(offset, limit)); + Page userTenantPage = userTenantRepository.findByUserId(userId, OffsetRequest.of(offset, limit)); if (userTenantPage.getContent().isEmpty()) { throw new ResourceNotFoundException(USER_ID, String.valueOf(userId)); diff --git a/src/main/resources/db/changelog/changes/create-tenant-table.xml b/src/main/resources/db/changelog/changes/create-tenant-table.xml index 3fef6a75..08644d80 100644 --- a/src/main/resources/db/changelog/changes/create-tenant-table.xml +++ b/src/main/resources/db/changelog/changes/create-tenant-table.xml @@ -55,4 +55,12 @@ + + + + + + + + diff --git a/src/main/resources/log4j2-json.properties b/src/main/resources/log4j2-json.properties index a8c80275..7d4a507d 100644 --- a/src/main/resources/log4j2-json.properties +++ b/src/main/resources/log4j2-json.properties @@ -31,3 +31,7 @@ appender.console.layout.moduleId.value = $${folio:moduleid:-} rootLogger.level = info rootLogger.appenderRefs = info rootLogger.appenderRef.stdout.ref = STDOUT + +logger.kafka_consumer_config.name = org.apache.kafka.clients.consumer.ConsumerConfig +logger.kafka_consumer_config.level = error +logger.kafka_consumer_config.appenderRef.stdout.ref = STDOUT diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties index 4996d068..c41432bc 100644 --- a/src/main/resources/log4j2.properties +++ b/src/main/resources/log4j2.properties @@ -13,3 +13,7 @@ appender.console.layout.pattern = %d{HH:mm:ss} %t [$${folio:requestid:-}] [$${fo rootLogger.level = info rootLogger.appenderRefs = info rootLogger.appenderRef.stdout.ref = STDOUT + +logger.kafka_consumer_config.name = org.apache.kafka.clients.consumer.ConsumerConfig +logger.kafka_consumer_config.level = error +logger.kafka_consumer_config.appenderRef.stdout.ref = STDOUT diff --git a/src/main/resources/permissions/system-user-permissions.csv b/src/main/resources/permissions/system-user-permissions.csv index 4a2d8d35..8d992b54 100644 --- a/src/main/resources/permissions/system-user-permissions.csv +++ b/src/main/resources/permissions/system-user-permissions.csv @@ -11,6 +11,7 @@ perms.users.assign.immutable perms.users.assign.mutable perms.users.get user-tenants.item.post +user-tenants.item.delete consortia.sync-primary-affiliations.item.post consortia.create-primary-affiliations.item.post departments.item.post @@ -34,6 +35,9 @@ inventory-storage.call-number-types.item.delete inventory-storage.classification-types.item.post inventory-storage.classification-types.item.put inventory-storage.classification-types.item.delete +inventory-storage.contributor-name-types.item.post +inventory-storage.contributor-name-types.item.put +inventory-storage.contributor-name-types.item.delete inventory-storage.electronic-access-relationships.item.post inventory-storage.electronic-access-relationships.item.put inventory-storage.electronic-access-relationships.item.delete @@ -43,6 +47,9 @@ inventory-storage.holdings-note-types.item.delete inventory-storage.holdings-types.item.post inventory-storage.holdings-types.item.put inventory-storage.holdings-types.item.delete +inventory-storage.holdings-sources.item.post +inventory-storage.holdings-sources.item.put +inventory-storage.holdings-sources.item.delete inventory-storage.statistical-codes.item.post inventory-storage.statistical-codes.item.put inventory-storage.statistical-codes.item.delete diff --git a/src/main/resources/swagger.api/schemas/tenant.yaml b/src/main/resources/swagger.api/schemas/tenant.yaml index 8d3bbaef..b2044a42 100644 --- a/src/main/resources/swagger.api/schemas/tenant.yaml +++ b/src/main/resources/swagger.api/schemas/tenant.yaml @@ -14,6 +14,8 @@ Tenant: maxLength: 150 isCentral: type: boolean + isDeleted: + type: boolean additionalProperties: false required: - id diff --git a/src/main/resources/swagger.api/schemas/user.yaml b/src/main/resources/swagger.api/schemas/user.yaml index 428626a3..cdbe65e5 100644 --- a/src/main/resources/swagger.api/schemas/user.yaml +++ b/src/main/resources/swagger.api/schemas/user.yaml @@ -64,7 +64,7 @@ User: description: Object that contains custom field type: object additionalProperties: true - additionalProperties: false + additionalProperties: true Personal: type: object @@ -103,7 +103,11 @@ Personal: preferredContactTypeId: description: Id of user's preferred contact type type: string - additionalProperties: false + profilePictureLink: + description: Link to the profile picture + type: string + format: uri + additionalProperties: true required: - lastName diff --git a/src/test/java/org/folio/consortia/FolioConsortiaApplicationTest.java b/src/test/java/org/folio/consortia/FolioConsortiaApplicationTest.java index e4b3e55b..3f8082ac 100644 --- a/src/test/java/org/folio/consortia/FolioConsortiaApplicationTest.java +++ b/src/test/java/org/folio/consortia/FolioConsortiaApplicationTest.java @@ -30,7 +30,7 @@ public ResponseEntity deleteTenant(String operationId) { } @Override - public ResponseEntity postTenant(@Valid TenantAttributes tenantAttributes) { + public ResponseEntity postTenant(TenantAttributes tenantAttributes) { return ResponseEntity.status(HttpStatus.CREATED).build(); } } diff --git a/src/test/java/org/folio/consortia/base/BaseIT.java b/src/test/java/org/folio/consortia/base/BaseIT.java index 4171dc10..4de321b3 100644 --- a/src/test/java/org/folio/consortia/base/BaseIT.java +++ b/src/test/java/org/folio/consortia/base/BaseIT.java @@ -5,9 +5,16 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.util.List; - +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.common.ClasspathFileSource; import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine; +import java.util.ArrayList; +import java.util.List; +import lombok.SneakyThrows; import org.folio.consortia.support.extension.EnableKafkaExtension; import org.folio.spring.integration.XOkapiHeaders; import org.folio.tenant.domain.dto.TenantAttributes; @@ -32,13 +39,6 @@ import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Testcontainers; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.tomakehurst.wiremock.WireMockServer; - -import lombok.SneakyThrows; - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ContextConfiguration(initializers = BaseIT.DockerPostgresDataSourceInitializer.class) @@ -69,7 +69,7 @@ public abstract class BaseIT { static void beforeAll(@Autowired MockMvc mockMvc) { wireMockServer = new WireMockServer(wireMockConfig() .port(WIRE_MOCK_PORT) - .extensions(new ResponseTemplateTransformer(true))); + .extensions(new ResponseTemplateTransformer(TemplateEngine.defaultTemplateEngine(), true, new ClasspathFileSource("/"), new ArrayList<>()))); wireMockServer.start(); diff --git a/src/test/java/org/folio/consortia/controller/ConsortiumControllerTest.java b/src/test/java/org/folio/consortia/controller/ConsortiumControllerTest.java index 25c569ac..eb6676dd 100644 --- a/src/test/java/org/folio/consortia/controller/ConsortiumControllerTest.java +++ b/src/test/java/org/folio/consortia/controller/ConsortiumControllerTest.java @@ -10,20 +10,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.folio.consortia.domain.entity.ConsortiumEntity; -import org.folio.consortia.exception.ResourceAlreadyExistException; -import org.folio.consortia.repository.ConsortiumRepository; import java.util.ArrayList; import java.util.List; import java.util.Optional; - import org.folio.consortia.base.BaseIT; +import org.folio.consortia.domain.entity.ConsortiumEntity; +import org.folio.consortia.exception.ResourceAlreadyExistException; +import org.folio.consortia.repository.ConsortiumRepository; +import org.folio.spring.data.OffsetRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; class ConsortiumControllerTest extends BaseIT { private static final String CONSORTIUM_RESOURCE_EXIST_MSG_TEMPLATE = "System can not have more than one consortium record"; @@ -161,8 +160,8 @@ void shouldGetConsortiumCollection() throws Exception { List consortiumEntityList = new ArrayList<>(); consortiumEntityList.add(consortiumEntity); - when(consortiumRepository.findAll(PageRequest.of(0, 1))) - .thenReturn(new PageImpl<>(consortiumEntityList, PageRequest.of(0, 1), consortiumEntityList.size())); + when(consortiumRepository.findAll(OffsetRequest.of(0, 1))) + .thenReturn(new PageImpl<>(consortiumEntityList, OffsetRequest.of(0, 1), consortiumEntityList.size())); this.mockMvc.perform( get("/consortia") diff --git a/src/test/java/org/folio/consortia/controller/TenantControllerTest.java b/src/test/java/org/folio/consortia/controller/TenantControllerTest.java index 6b9fcfba..da01187d 100644 --- a/src/test/java/org/folio/consortia/controller/TenantControllerTest.java +++ b/src/test/java/org/folio/consortia/controller/TenantControllerTest.java @@ -35,12 +35,14 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import org.folio.consortia.base.BaseIT; import org.folio.consortia.client.CapabilitiesClient; import org.folio.consortia.client.ConsortiaConfigurationClient; import org.folio.consortia.client.SyncPrimaryAffiliationClient; import org.folio.consortia.client.UserCapabilitiesClient; import org.folio.consortia.client.UserPermissionsClient; import org.folio.consortia.client.UserTenantsClient; +import org.folio.consortia.client.UsersClient; import org.folio.consortia.client.UsersKeycloakClient; import org.folio.consortia.config.kafka.KafkaService; import org.folio.consortia.domain.dto.Capabilities; @@ -63,9 +65,10 @@ import org.folio.consortia.service.UserService; import org.folio.consortia.service.UserTenantService; import org.folio.consortia.service.impl.ConsortiaConfigurationServiceImpl; -import org.folio.consortia.base.BaseIT; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; +import org.folio.spring.context.ExecutionContextBuilder; +import org.folio.spring.data.OffsetRequest; import org.folio.spring.integration.XOkapiHeaders; import org.folio.spring.service.PrepareSystemUserService; import org.folio.spring.service.SystemUserScopedExecutionService; @@ -77,7 +80,6 @@ import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @@ -86,6 +88,7 @@ class TenantControllerTest extends BaseIT { private static final String TENANT_REQUEST_BODY = "{\"id\":\"diku1234\",\"code\":\"TST\",\"name\":\"diku_tenant_name1234\", \"isCentral\":false}"; private static final String CONSORTIUM_ID = "7698e46-c3e3-11ed-afa1-0242ac120002"; + private static final String CENTRAL_TENANT_REQUEST_BODY = "{\"id\":\"diku1234\",\"code\":\"TST\",\"name\":\"diku_tenant_name1234\", \"isCentral\":true}"; private static final String CENTRAL_TENANT_ID = "diku"; public static final String SYNC_PRIMARY_AFFILIATIONS_URL = "/consortia/%s/tenants/%s/sync-primary-affiliations?centralTenantId=%s"; @@ -130,6 +133,10 @@ class TenantControllerTest extends BaseIT { SyncPrimaryAffiliationClient syncPrimaryAffiliationClient; @MockBean UsersKeycloakClient usersKeycloakClient; + @MockBean + UsersClient usersClient; + @MockBean + ExecutionContextBuilder executionContextBuilder; /* Success cases */ @Test @@ -141,13 +148,12 @@ void getTenants() throws Exception { tenantEntityList.add(tenantEntity1); tenantEntityList.add(tenantEntity2); - when(tenantRepository.findByConsortiumId(any(), any(PageRequest.of(0, 2) - .getClass()))).thenReturn(new PageImpl<>(tenantEntityList, PageRequest.of(0, 2), tenantEntityList.size())); + when(tenantRepository.findByConsortiumId(any(), any(OffsetRequest.of(1, 2).getClass()))) + .thenReturn(new PageImpl<>(tenantEntityList, OffsetRequest.of(2, 2), tenantEntityList.size())); when(consortiumRepository.existsById(consortiumId)).thenReturn(true); var headers = defaultHeaders(); - this.mockMvc.perform( - get("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/tenants?limit=2&offset=1").headers(headers)) + this.mockMvc.perform(get("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/tenants?limit=2&offset=2").headers(headers)) .andExpectAll(status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE)); } @@ -198,13 +204,40 @@ void shouldSaveTenant(String contentString) throws Exception { @ParameterizedTest @ValueSource(strings = {TENANT_REQUEST_BODY}) - void shouldUpdateTenant(String contentString) throws Exception { - TenantEntity tenant = createTenantEntity(); + void shouldReAddSoftDeletedTenant(String contentString) throws Exception { + var headers = defaultHeaders(); + String adminUser = UUID.randomUUID().toString(); + TenantEntity centralTenant = createTenantEntity(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID, "AAA", true); + var existedTenant = createTenantEntity("diku1234", "diku_tenant_name1234"); + existedTenant.setIsDeleted(true); + var tenantDetailsEntity = new TenantDetailsEntity(); + tenantDetailsEntity.setConsortiumId(centralTenant.getConsortiumId()); + tenantDetailsEntity.setId("diku1234"); + + doNothing().when(userTenantsClient).postUserTenant(any()); + when(consortiumRepository.existsById(any())).thenReturn(true); + when(tenantRepository.findById("diku1234")).thenReturn(Optional.of(existedTenant)); + when(tenantDetailsRepository.save(any(TenantDetailsEntity.class))).thenReturn(tenantDetailsEntity); + when(tenantRepository.findCentralTenant()).thenReturn(Optional.of(centralTenant)); + doReturn(folioExecutionContext).when(executionContextBuilder).buildContext(anyString()); + + this.mockMvc.perform( + post("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/tenants?adminUserId=" + adminUser) + .headers(headers).content(contentString)) + .andExpect(status().isCreated()); + } + + @ParameterizedTest + @ValueSource(strings = {TENANT_REQUEST_BODY}) + void shouldUpdateTenant(String contentString) throws Exception { + var existingTenant = createTenantEntity(); + var updatedTenant = createTenantEntity(); var headers = defaultHeaders(); - when(tenantRepository.existsById(any())).thenReturn(true); + + when(tenantRepository.findById(anyString())).thenReturn(Optional.of(existingTenant)); when(consortiumRepository.existsById(any())).thenReturn(true); - when(tenantRepository.save(any())).thenReturn(tenant); + when(tenantRepository.save(any())).thenReturn(updatedTenant); this.mockMvc.perform( put("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/tenants/diku1234") @@ -240,7 +273,7 @@ void getBadRequest() throws Exception { andExpectAll( status().is4xxClientError(), content().contentType(MediaType.APPLICATION_JSON_VALUE), - jsonPath("$.errors[0].message", is("Page size must not be less than one"))); + jsonPath("$.errors[0].message", is("Limit cannot be negative or zero: 0"))); } @Test @@ -330,20 +363,38 @@ void shouldThrownMethodArgumentNotValidException(String contentString) throws Ex @ParameterizedTest - @ValueSource(strings = {TENANT_REQUEST_BODY}) - void shouldGet4xxErrorWhileSavingDuplicateName(String contentString) throws Exception { + @ValueSource(strings = {CENTRAL_TENANT_REQUEST_BODY}) + void shouldGet4xxErrorWhileSavingDuplicateCentralTenant(String contentString) throws Exception { var headers = defaultHeaders(); UUID consortiumId = UUID.fromString(CONSORTIUM_ID); TenantEntity centralTenant = createTenantEntity(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID, "TTA", true); - PermissionUser permissionUser = new PermissionUser(); - PermissionUserCollection permissionUserCollection = new PermissionUserCollection(); - permissionUserCollection.setPermissionUsers(List.of(permissionUser)); doReturn(new User()).when(usersKeycloakClient).getUsersByUserId(any()); when(consortiumRepository.existsById(consortiumId)).thenReturn(true); when(tenantRepository.existsById(any(String.class))).thenReturn(true); + when(tenantRepository.existsByIsCentralTrue()).thenReturn(true); when(tenantRepository.findCentralTenant()).thenReturn(Optional.of(centralTenant)); - doNothing().when(configurationClient).saveConfiguration(createConsortiaConfiguration(CENTRAL_TENANT_ID)); + + this.mockMvc.perform( + post("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/tenants?adminUserId=111841e3-e6fb-4191-9fd8-5674a5107c34") + .headers(headers).content(contentString)) + .andExpectAll( + status().is4xxClientError(), + jsonPath("$.errors[0].message", is("Object with isCentral [true] is already presented in the system")), + jsonPath("$.errors[0].code", is("DUPLICATE_ERROR"))); + } + + @ParameterizedTest + @ValueSource(strings = {TENANT_REQUEST_BODY}) + void shouldGet4xxErrorWhileSavingExistingTenant(String contentString) throws Exception { + var headers = defaultHeaders(); + UUID consortiumId = UUID.fromString(CONSORTIUM_ID); + var existedTenant = createTenantEntity("diku1234", "diku_tenant_name1234"); + existedTenant.setIsDeleted(false); + + when(consortiumRepository.existsById(consortiumId)).thenReturn(true); + when(tenantRepository.existsById(any(String.class))).thenReturn(true); + when(tenantRepository.findById(anyString())).thenReturn(Optional.of(existedTenant)); this.mockMvc.perform( post("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/tenants?adminUserId=111841e3-e6fb-4191-9fd8-5674a5107c34") @@ -358,10 +409,10 @@ void shouldGet4xxErrorWhileSavingDuplicateName(String contentString) throws Exce @ValueSource(strings = {TENANT_REQUEST_BODY}) void shouldThrowValidationErrorWhileUpdateTenant(String contentString) throws Exception { var headers = defaultHeaders(); + var existingTenant = createTenantEntity(); - when(tenantRepository.existsById(any())).thenReturn(true); + when(tenantRepository.findById(any())).thenReturn(Optional.of(existingTenant)); when(consortiumRepository.existsById(any())).thenReturn(true); - when(tenantRepository.save(any(TenantEntity.class))).thenReturn(TenantEntity.class.newInstance()); this.mockMvc.perform( put("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/tenants/TestID") @@ -390,20 +441,23 @@ void shouldThrowNotFoundErrorWhileUpdateTenant(String contentString) throws Exce jsonPath("$.errors[0].code", is("NOT_FOUND_ERROR"))); } - @ParameterizedTest - @ValueSource(strings = {TENANT_REQUEST_BODY}) - void shouldThrownHasActiveAffiliationExceptionWhileDeletingTenant(String contentString) throws Exception { + @Test + void shouldThrownExceptionWhenDeletingCentralTenant() throws Exception { var headers = defaultHeaders(); - when(tenantRepository.existsById(any())).thenReturn(true); + String tenantId = "diku"; + var centralTenant = createTenantEntity(tenantId); + centralTenant.setIsCentral(true); + + when(tenantRepository.findById(any())).thenReturn(Optional.of(centralTenant)); when(consortiumRepository.existsById(any())).thenReturn(true); - when(userTenantRepository.existsByTenantId("diku1234")).thenReturn(true); this.mockMvc.perform( - delete("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/tenants/diku1234") - .headers(headers).content(contentString)) + delete("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/tenants/diku") + .headers(headers)) .andExpectAll( status().is4xxClientError(), - jsonPath("$.errors[0].code", is("VALIDATION_ERROR"))); + jsonPath("$.errors[0].code", is("VALIDATION_ERROR")), + jsonPath("$.errors[0].message", is("Central tenant [diku] cannot be deleted."))); } @Test diff --git a/src/test/java/org/folio/consortia/controller/UserTenantControllerTest.java b/src/test/java/org/folio/consortia/controller/UserTenantControllerTest.java index 2fff53f8..950b123b 100644 --- a/src/test/java/org/folio/consortia/controller/UserTenantControllerTest.java +++ b/src/test/java/org/folio/consortia/controller/UserTenantControllerTest.java @@ -1,8 +1,8 @@ package org.folio.consortia.controller; -import static org.folio.consortia.support.TestConstants.CENTRAL_TENANT_ID; import static org.folio.consortia.support.EntityUtils.createUserTenant; import static org.folio.consortia.support.EntityUtils.createUserTenantEntity; +import static org.folio.consortia.support.TestConstants.CENTRAL_TENANT_ID; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; @@ -15,22 +15,26 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.folio.consortia.client.UsersKeycloakClient; -import org.folio.consortia.domain.entity.UserTenantEntity; -import org.folio.consortia.exception.ResourceNotFoundException; -import org.folio.consortia.repository.ConsortiumRepository; -import org.folio.consortia.repository.UserTenantRepository; -import org.folio.consortia.service.TenantService; -import org.folio.consortia.service.UserTenantService; +import feign.FeignException; +import feign.Request; +import feign.RequestTemplate; +import feign.Response; import java.nio.charset.Charset; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; - +import org.folio.consortia.base.BaseIT; +import org.folio.consortia.client.UsersKeycloakClient; import org.folio.consortia.domain.dto.UserTenant; import org.folio.consortia.domain.dto.UserTenantCollection; -import org.folio.consortia.base.BaseIT; +import org.folio.consortia.domain.entity.UserTenantEntity; +import org.folio.consortia.exception.ResourceNotFoundException; +import org.folio.consortia.repository.ConsortiumRepository; +import org.folio.consortia.repository.UserTenantRepository; +import org.folio.consortia.service.TenantService; +import org.folio.consortia.service.UserTenantService; +import org.folio.spring.data.OffsetRequest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -42,23 +46,18 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import feign.FeignException; -import feign.Request; -import feign.RequestTemplate; -import feign.Response; - @EntityScan(basePackageClasses = UserTenantEntity.class) class UserTenantControllerTest extends BaseIT { private static final String CONSORTIUM_ID = "7698e46-c3e3-11ed-afa1-0242ac120002"; private static final String PERMISSION_EXCEPTION_MSG = "[403 Forbidden] during [GET] to " + - "[http://users/8c54ff1e-5954-4227-8402-9a5dd061a350] [UsersClient#getUsersByUserId(String)]: " + + "[http://users/8c54ff1e-5954-4227-8402-9a5dd061a350] [UsersClient#getUserById(String)]: " + "[Access for user 'ss_admin' (b82b46b6-9a6e-46f0-b986-5c643d9ba036) requires permission: users.item.get]"; + @Mock private UserTenantService userTenantService; @InjectMocks @@ -122,7 +121,7 @@ void shouldGetUserTenantList() throws Exception { Page userTenantPage = new PageImpl<>(List.of(createUserTenantEntity(consortiumId))); when(consortiumRepository.existsById(consortiumId)).thenReturn(true); - when(userTenantRepository.findAll(PageRequest.of(1, 2))).thenReturn(userTenantPage); + when(userTenantRepository.getAll(OffsetRequest.of(1, 2))).thenReturn(userTenantPage); this.mockMvc.perform( get("/consortia/7698e46-c3e3-11ed-afa1-0242ac120002/user-tenants?limit=2&offset=1") @@ -223,6 +222,7 @@ private Response createForbiddenResponse(String message) { .request(request) .build(); } + private Response createUnknownResponse(String message) { Request request = Request.create(Request.HttpMethod.GET, "", Map.of(), null, Charset.defaultCharset(), new RequestTemplate()); diff --git a/src/test/java/org/folio/consortia/service/ConsortiumServiceTest.java b/src/test/java/org/folio/consortia/service/ConsortiumServiceTest.java index 21cddfa3..f3d9670a 100644 --- a/src/test/java/org/folio/consortia/service/ConsortiumServiceTest.java +++ b/src/test/java/org/folio/consortia/service/ConsortiumServiceTest.java @@ -6,6 +6,7 @@ import org.folio.consortia.repository.ConsortiumRepository; import org.folio.consortia.service.impl.ConsortiumServiceImpl; import org.folio.consortia.domain.dto.Consortium; +import org.folio.spring.data.OffsetRequest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -15,7 +16,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; import java.util.ArrayList; import java.util.List; @@ -44,8 +44,8 @@ void shouldGetConsortiumList() { List consortiumEntityList = new ArrayList<>(); consortiumEntityList.add(consortiumEntity); - when(consortiumRepository.findAll(PageRequest.of(0, 1))) - .thenReturn(new PageImpl<>(consortiumEntityList, PageRequest.of(0, 1), consortiumEntityList.size())); + when(consortiumRepository.findAll(OffsetRequest.of(0, 1))) + .thenReturn(new PageImpl<>(consortiumEntityList, OffsetRequest.of(0, 1), consortiumEntityList.size())); var consortiumCollection = consortiumService.getAll(); Assertions.assertEquals(1, consortiumCollection.getTotalRecords()); diff --git a/src/test/java/org/folio/consortia/service/SharingInstanceServiceTest.java b/src/test/java/org/folio/consortia/service/SharingInstanceServiceTest.java index ec6a9809..527369f8 100644 --- a/src/test/java/org/folio/consortia/service/SharingInstanceServiceTest.java +++ b/src/test/java/org/folio/consortia/service/SharingInstanceServiceTest.java @@ -205,6 +205,47 @@ void shouldSaveSharingInstanceWhenSourceTenantEqualsCentralTenantAndAllRequestsS verify(sharingInstanceRepository, times(1)).save(any()); } + @Test + void shouldUpdatePreviousSharingInstanceWhenAfterSuccessfulUpdate() throws JsonProcessingException { + String sourceTenantId = "mobius"; + String targetTenantId = "college"; + SharingInstance sharingInstance = createSharingInstance(instanceIdentifier, sourceTenantId, targetTenantId); + SharingInstanceEntity sharingInstanceEntity = new SharingInstanceEntity(); + SharingInstanceEntity existingSharingInstanceEntity = createSharingInstanceEntity(sharingInstance.getId(), instanceIdentifier, sourceTenantId, targetTenantId); + SharingInstanceEntity updatingSharingInstanceEntity = createSharingInstanceEntity(sharingInstance.getId(), instanceIdentifier, sourceTenantId, targetTenantId); + updatingSharingInstanceEntity.setStatus(Status.COMPLETE); + + // skip validation part + when(consortiumRepository.existsById(any())).thenReturn(true); + doNothing().when(tenantService).checkTenantExistsOrThrow(anyString()); + when(folioExecutionContext.getOkapiHeaders()).thenReturn(headers); + + when(tenantService.getCentralTenantId()).thenReturn("mobius"); + when(conversionService.convert(any(), eq(SharingInstance.class))).thenReturn(toDto(sharingInstanceEntity)); + when(sharingInstanceRepository.findByInstanceAndTenantIds(instanceIdentifier, sourceTenantId, targetTenantId)) + .thenReturn(Optional.of(existingSharingInstanceEntity)); + + // existing object should be updated, not created new one + when(sharingInstanceRepository.save(updatingSharingInstanceEntity)).thenReturn(updatingSharingInstanceEntity); + + // return instance as JsonNode when getting + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode inventoryInstance = objectMapper.readTree("{ \"source\" : \"folio\" } "); + + when(inventoryService.getById(any())).thenReturn(inventoryInstance); + + // do nothing when posting inventory instance + doNothing().when(inventoryService).saveInstance(anyString()); + + // verify status field gets updated and save() method gets called + sharingInstanceService.start(UUID.randomUUID(), sharingInstance); + + assertThat(sharingInstance.getError()).isNull(); + assertThat(sharingInstance.getStatus()).isEqualTo(Status.COMPLETE); + verify(sharingInstanceRepository, times(1)).save(any()); + verify(sharingInstanceRepository, times(1)).findByInstanceAndTenantIds(any(), anyString(), anyString()); + } + @Test void shouldPromoteSharingInstanceWithCompleteStatus() throws JsonProcessingException { SharingInstance sharingInstance = createSharingInstance(instanceIdentifier, "college", "mobius"); diff --git a/src/test/java/org/folio/consortia/service/TenantServiceTest.java b/src/test/java/org/folio/consortia/service/TenantServiceTest.java index 4c20844e..c05e4c0a 100644 --- a/src/test/java/org/folio/consortia/service/TenantServiceTest.java +++ b/src/test/java/org/folio/consortia/service/TenantServiceTest.java @@ -1,6 +1,8 @@ package org.folio.consortia.service; +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; @@ -19,8 +21,18 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; import org.folio.consortia.client.ConsortiaConfigurationClient; import org.folio.consortia.client.PermissionsClient; import org.folio.consortia.client.SyncPrimaryAffiliationClient; @@ -30,6 +42,10 @@ import org.folio.consortia.config.kafka.KafkaService; import org.folio.consortia.domain.dto.PermissionUser; import org.folio.consortia.domain.dto.PermissionUserCollection; +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.UserCollection; import org.folio.consortia.domain.entity.TenantDetailsEntity; import org.folio.consortia.domain.entity.TenantEntity; import org.folio.consortia.exception.ResourceAlreadyExistException; @@ -39,27 +55,16 @@ import org.folio.consortia.repository.TenantRepository; import org.folio.consortia.repository.UserTenantRepository; import org.folio.consortia.service.impl.TenantServiceImpl; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -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.UserCollection; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; +import org.folio.spring.context.ExecutionContextBuilder; +import org.folio.spring.data.OffsetRequest; import org.folio.spring.integration.XOkapiHeaders; import org.folio.spring.service.SystemUserScopedExecutionService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration; @@ -67,10 +72,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; @SpringBootTest @EnableAutoConfiguration(exclude = BatchAutoConfiguration.class) @@ -78,7 +79,7 @@ class TenantServiceTest { private final static String CONSORTIUM_ID = "7698e46-c3e3-11ed-afa1-0242ac120002"; - private static final String CENTRAL_TENANT_ID = "diku"; + @InjectMocks private TenantServiceImpl tenantService; @Mock @@ -96,7 +97,8 @@ class TenantServiceTest { @Mock private ConsortiumService consortiumService; @Mock - private FolioExecutionContext folioExecutionContext = new FolioExecutionContext() {}; + private FolioExecutionContext folioExecutionContext = new FolioExecutionContext() { + }; @Mock private ConsortiaConfigurationClient configurationClient; @Mock @@ -127,6 +129,8 @@ class TenantServiceTest { private SystemUserScopedExecutionService systemUserScopedExecutionService; @Mock private LockService lockService; + @Mock + private ExecutionContextBuilder executionContextBuilder; @Test void shouldGetTenantList() { @@ -141,11 +145,11 @@ void shouldGetTenantList() { when(consortiumRepository.existsById(consortiumId)).thenReturn(true); when(tenantRepository.existsById(any())).thenReturn(true); - when(tenantRepository.findByConsortiumId(any(), any(PageRequest.of(offset, limit).getClass()))) - .thenReturn(new PageImpl<>(tenantEntityList, PageRequest.of(offset, limit), tenantEntityList.size())); + when(tenantRepository.findByConsortiumId(any(), any(OffsetRequest.of(offset, limit).getClass()))) + .thenReturn(new PageImpl<>(tenantEntityList, OffsetRequest.of(offset, limit), tenantEntityList.size())); var tenantCollection = tenantService.get(consortiumId, 0, 10); - Assertions.assertEquals(2, tenantCollection.getTotalRecords()); + assertEquals(2, tenantCollection.getTotalRecords()); } @Test @@ -162,7 +166,7 @@ void shouldGetAllTenantList() { when(tenantRepository.findByConsortiumId(consortiumId)).thenReturn(tenantEntityList); var allTenants = tenantService.getAll(consortiumId); - Assertions.assertEquals(2, allTenants.getTotalRecords()); + assertEquals(2, allTenants.getTotalRecords()); } @Test @@ -188,12 +192,12 @@ 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(CENTRAL_TENANT_ID)); + doNothing().when(configurationClient).saveConfiguration(createConsortiaConfiguration(TENANT_ID)); doNothing().when(userTenantsClient).postUserTenant(any()); when(conversionService.convert(localTenantDetailsEntity, Tenant.class)).thenReturn(tenant); doAnswer(TenantServiceTest::runSecondArgument) .when(systemUserScopedExecutionService).executeAsyncSystemUserScoped(anyString(), any()); - when(folioExecutionContext.getTenantId()).thenReturn("diku"); + when(folioExecutionContext.getTenantId()).thenReturn(TENANT_ID); var tenant1 = tenantService.save(consortiumId, UUID.fromString(adminUser.getId()), tenant); @@ -206,7 +210,7 @@ void shouldSaveNotCentralTenantWithNewUserAndPermissions() { verify(userService, times(1)).getByUsername(any()); verify(lockService).lockTenantSetupWithinTransaction(); - Assertions.assertEquals(tenant, tenant1); + assertEquals(tenant, tenant1); } @Test @@ -214,7 +218,7 @@ void shouldSaveCentralTenantWithExistingAndPermissions() throws JsonProcessingEx UUID consortiumId = UUID.fromString(CONSORTIUM_ID); TenantDetailsEntity tenantDetailsEntity = createTenantDetailsEntity("ABC1", "TestName1"); Tenant tenant = createTenant("TestID", "Test", true); - TenantEntity centralTenant = createTenantEntity("diku", "diku"); + TenantEntity centralTenant = createTenantEntity(TENANT_ID); PermissionUser permissionUser = new PermissionUser(); permissionUser.setPermissions(List.of("users.collection.get")); PermissionUserCollection permissionUserCollection = new PermissionUserCollection(); @@ -231,7 +235,7 @@ void shouldSaveCentralTenantWithExistingAndPermissions() throws JsonProcessingEx 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(CENTRAL_TENANT_ID)); + doNothing().when(configurationClient).saveConfiguration(createConsortiaConfiguration(TENANT_ID)); doNothing().when(userTenantsClient).postUserTenant(any()); when(conversionService.convert(tenantDetailsEntity, Tenant.class)).thenReturn(tenant); doAnswer(TenantServiceTest::runSecondArgument) @@ -241,6 +245,8 @@ void shouldSaveCentralTenantWithExistingAndPermissions() throws JsonProcessingEx okapiHeaders.put(XOkapiHeaders.TENANT, List.of("diku")); when(folioExecutionContext.getOkapiHeaders()).thenReturn(okapiHeaders); when(usersClient.getUserCollection(anyString(), anyInt(), anyInt())).thenReturn(userCollection); + doReturn(folioExecutionContext).when(executionContextBuilder).buildContext(anyString()); + mockOkapiHeaders(); var tenant1 = tenantService.save(consortiumId, UUID.randomUUID(), tenant); @@ -253,24 +259,54 @@ void shouldSaveCentralTenantWithExistingAndPermissions() throws JsonProcessingEx verify(userService, never()).createUser(any()); verify(permissionUserService, never()).createWithPermissionsFromFile(any(), any()); - Assertions.assertEquals(tenant, tenant1); + assertEquals(tenant, tenant1); } + @Test + void shouldReAddSoftDeletedTenant() { + UUID consortiumId = UUID.fromString(CONSORTIUM_ID); + Tenant newTenant = createTenant("TestID", "Test", false); + TenantEntity existedTenant = createTenantEntity("TestID"); + existedTenant.setIsDeleted(true); + TenantDetailsEntity savedTenantDetailsEntity = createTenantDetailsEntity("ABC1", "TestName1"); + + TenantEntity centralTenant = createTenantEntity(TENANT_ID); + when(consortiumRepository.existsById(consortiumId)).thenReturn(true); + when(tenantRepository.findById(newTenant.getId())).thenReturn(Optional.of(existedTenant)); + when(tenantRepository.findCentralTenant()).thenReturn(Optional.of(centralTenant)); + 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(conversionService.convert(savedTenantDetailsEntity, Tenant.class)).thenReturn(newTenant); + doReturn(folioExecutionContext).when(executionContextBuilder).buildContext(anyString()); + mockOkapiHeaders(); + + var actualTenant = tenantService.save(consortiumId, UUID.randomUUID(), newTenant); + + verifyNoInteractions(configurationClient); + verifyNoInteractions(lockService); + + verifyNoInteractions(userService); + verifyNoInteractions(userTenantRepository); + verify(userTenantsClient).postUserTenant(any()); + verifyNoInteractions(userService); + verifyNoInteractions(permissionUserService); + + assertEquals(newTenant, actualTenant); + } @Test void shouldUpdateTenant() { UUID consortiumId = UUID.randomUUID(); - TenantEntity tenantEntity1 = createTenantEntity("TestID", "TestName1"); + TenantEntity existingTenant = createTenantEntity("TestID", "TestName1"); Tenant tenant = createTenant("TestID", "TestName2"); - Map> okapiHeaders = new HashMap<>(); - okapiHeaders.put(XOkapiHeaders.TENANT, List.of("diku")); - when(folioExecutionContext.getOkapiHeaders()).thenReturn(okapiHeaders); - when(consortiumRepository.existsById(consortiumId)).thenReturn(true); - when(tenantRepository.existsById(any())).thenReturn(true); - when(tenantRepository.save(any(TenantEntity.class))).thenReturn(tenantEntity1); - when(conversionService.convert(tenantEntity1, Tenant.class)).thenReturn(tenant); + when(tenantRepository.findById(any())).thenReturn(Optional.of(existingTenant)); + when(tenantRepository.save(any(TenantEntity.class))).thenReturn(existingTenant); + when(conversionService.convert(existingTenant, Tenant.class)).thenReturn(tenant); + mockOkapiHeaders(); var tenant1 = tenantService.update(UUID.fromString(CONSORTIUM_ID), tenant.getId(), tenant); Assertions.assertEquals(tenant.getId(), tenant1.getId()); @@ -280,48 +316,62 @@ void shouldUpdateTenant() { @Test void shouldDeleteTenant() { UUID consortiumId = UUID.randomUUID(); - String tenantId = "diku"; + var tenant = createTenantEntity(TENANT_ID); + var deletingTenant = createTenantEntity(TENANT_ID); + deletingTenant.setIsDeleted(true); doNothing().when(consortiumService).checkConsortiumExistsOrThrow(consortiumId); - when(tenantRepository.existsById(any())).thenReturn(true); doNothing().when(cleanupService).clearPublicationTables(); - doNothing().when(tenantRepository).deleteById(tenantId); + doReturn(deletingTenant).when(tenantRepository).save(deletingTenant); + when(tenantRepository.findById(tenant.getId())).thenReturn(Optional.of(tenant)); + doReturn(folioExecutionContext).when(executionContextBuilder).buildContext(anyString()); + mockOkapiHeaders(); - tenantService.delete(consortiumId, tenantId); + tenantService.delete(consortiumId, TENANT_ID); // Assert - Mockito.verify(consortiumService).checkConsortiumExistsOrThrow(consortiumId); - Mockito.verify(tenantRepository).existsById(tenantId); - Mockito.verify(tenantRepository).deleteById(tenantId); + verify(consortiumService).checkConsortiumExistsOrThrow(consortiumId); + verify(tenantRepository).findById(TENANT_ID); + verify(tenantRepository).save(deletingTenant); + verify(cleanupService).clearPublicationTables(); + verify(userTenantsClient).deleteUserTenants(); } - @Test() - void testDeleteWithAssociation() { + @Test + void testDeleteNonexistentTenant() { UUID consortiumId = UUID.randomUUID(); String tenantId = "123"; // Mock repository method calls - Mockito.when(tenantRepository.existsById(tenantId)).thenReturn(true); - Mockito.when(userTenantRepository.existsByTenantId(tenantId)).thenReturn(true); + when(tenantRepository.existsById(tenantId)).thenReturn(false); // Call the method - assertThrows(IllegalArgumentException.class, () -> + assertThrows(ResourceNotFoundException.class, () -> tenantService.delete(consortiumId, tenantId)); } @Test - void testDeleteNonexistentTenant() { + void shouldThrowErrorWhenDeletingCentralTenant() { UUID consortiumId = UUID.randomUUID(); - String tenantId = "123"; + var tenant = createTenantEntity(TENANT_ID); + tenant.setIsCentral(true); + var deletingTenant = createTenantEntity(TENANT_ID); + deletingTenant.setIsDeleted(true); - // Mock repository method calls - when(tenantRepository.existsById(tenantId)).thenReturn(false); + doNothing().when(consortiumService).checkConsortiumExistsOrThrow(consortiumId); + when(tenantRepository.findById(tenant.getId())).thenReturn(Optional.of(tenant)); - // Call the method - assertThrows(ResourceNotFoundException.class, () -> - tenantService.delete(consortiumId, tenantId)); + // Assert + assertThrows(java.lang.IllegalArgumentException.class, () -> + tenantService.delete(consortiumId, TENANT_ID)); + + verify(consortiumService).checkConsortiumExistsOrThrow(consortiumId); + verify(tenantRepository).findById(TENANT_ID); + verifyNoInteractions(cleanupService); + verifyNoInteractions(userTenantsClient); } + @Test void shouldThrowExceptionWhileSavingLocalTenantWithoutAdminUserId() { TenantDetailsEntity tenantDetailsEntity = createTenantDetailsEntity("TestID", "TestName1"); @@ -344,7 +394,7 @@ void shouldThrowExceptionWhileSavingWithDuplicateCodeOrName() { when(consortiumRepository.existsById(any())).thenReturn(true); when(tenantRepository.existsById(any())).thenReturn(false); when(tenantRepository.save(any(TenantEntity.class))).thenReturn(tenantEntity1); - when(tenantRepository.existsByCode(any())).thenReturn(true); + when(tenantRepository.existsByCodeForOtherTenant(anyString(), anyString())).thenReturn(true); when(conversionService.convert(tenantEntity1, Tenant.class)).thenReturn(tenant); assertThrows(ResourceAlreadyExistException.class, () -> @@ -353,13 +403,11 @@ void shouldThrowExceptionWhileSavingWithDuplicateCodeOrName() { @Test void shouldThrowExceptionWhileUpdateTenant() { - TenantEntity tenantEntity1 = createTenantEntity("TestID", "TestName1"); - Tenant tenant = createTenant("TestID", "TestName2"); + var existingTenant = createTenantEntity("TestID", "TestName1"); + var tenant = createTenant("TestID", "TestName2"); when(consortiumRepository.existsById(any())).thenReturn(true); - when(tenantRepository.existsById(any())).thenReturn(true); - when(tenantRepository.save(any(TenantEntity.class))).thenReturn(tenantEntity1); - when(conversionService.convert(tenantEntity1, Tenant.class)).thenReturn(tenant); + when(tenantRepository.findById(tenant.getId() + "1234")).thenReturn(Optional.of(existingTenant)); assertThrows(java.lang.IllegalArgumentException.class, () -> tenantService.update(UUID.fromString(CONSORTIUM_ID), tenant.getId() + "1234", tenant)); @@ -371,7 +419,6 @@ void shouldThrowNotFoundExceptionWhileUpdateTenant() { Tenant tenant = createTenant("TestID", "TestName2"); when(consortiumRepository.existsById(any())).thenReturn(true); - when(tenantRepository.save(any(TenantEntity.class))).thenReturn(tenantEntity1); when(conversionService.convert(tenantEntity1, Tenant.class)).thenReturn(tenant); assertThrows(ResourceNotFoundException.class, () -> @@ -379,31 +426,25 @@ void shouldThrowNotFoundExceptionWhileUpdateTenant() { } @Test - void shouldThrowResourceAlreadyExistExceptionWhileSavingCentralTenant() { + void shouldNotSaveExistingTenant() { UUID consortiumId = UUID.fromString(CONSORTIUM_ID); - Tenant tenant = createTenant("TestID", "Test", true); + Tenant tenant = createTenant("TestID", "Test"); + TenantEntity existedTenant = createTenantEntity("TestId"); when(consortiumRepository.existsById(consortiumId)).thenReturn(true); - when(tenantRepository.existsById(any())).thenReturn(false); - when(tenantRepository.existsByIsCentralTrue()).thenReturn(true); + when(tenantRepository.findById(tenant.getId())).thenReturn(Optional.of(existedTenant)); assertThrows(ResourceAlreadyExistException.class, () -> tenantService.save(UUID.fromString(CONSORTIUM_ID), null, tenant)); } @Test - void shouldNotSaveTenantForDuplicateId() { - TenantEntity tenantEntity1 = createTenantEntity("TestID", "Test"); + void shouldNotSaveDuplicateCentralTenant() { Tenant tenant = createTenant("TestID", "Testq", true); - TenantEntity centralTenant = createTenantEntity("diku", "diku"); - when(tenantRepository.existsById(any())).thenReturn(true); - when(conversionService.convert(tenantEntity1, Tenant.class)).thenReturn(tenant); - when(tenantRepository.findCentralTenant()).thenReturn(Optional.of(centralTenant)); - when(folioExecutionContext.getTenantId()).thenReturn("diku"); - Map> okapiHeaders = new HashMap<>(); - okapiHeaders.put(XOkapiHeaders.TENANT, List.of("diku")); - when(folioExecutionContext.getOkapiHeaders()).thenReturn(okapiHeaders); + when(consortiumRepository.existsById(UUID.fromString(CONSORTIUM_ID))).thenReturn(true); + when(tenantRepository.existsByIsCentralTrue()).thenReturn(true); + mockOkapiHeaders(); assertThrows(ResourceAlreadyExistException.class, () -> tenantService.save(UUID.fromString(CONSORTIUM_ID), null, tenant)); @@ -426,29 +467,33 @@ void shouldNotRetrieveEntityByTenantId() { @Test void shouldGetTenantDetails() { UUID consortiumId = UUID.randomUUID(); - String tenantId = "diku"; var tenantDetailsEntity = new TenantDetailsEntity(); var tenantDetailsExpected = new TenantDetails(); when(consortiumRepository.existsById(consortiumId)).thenReturn(true); - when(tenantDetailsRepository.findById(tenantId)).thenReturn(Optional.of(tenantDetailsEntity)); + when(tenantDetailsRepository.findById(TENANT_ID)).thenReturn(Optional.of(tenantDetailsEntity)); when(conversionService.convert(tenantDetailsEntity, TenantDetails.class)).thenReturn(tenantDetailsExpected); - var tenantDetails = tenantService.getTenantDetailsById(consortiumId, tenantId); + var tenantDetails = tenantService.getTenantDetailsById(consortiumId, TENANT_ID); assertEquals(tenantDetailsExpected, tenantDetails); } @Test void testGetTenantDetailsNonExistingTenant() { UUID consortiumId = UUID.randomUUID(); - String tenantId = "123"; when(consortiumRepository.existsById(consortiumId)).thenReturn(true); - when(tenantDetailsRepository.findById(tenantId)).thenReturn(Optional.empty()); + when(tenantDetailsRepository.findById(TENANT_ID)).thenReturn(Optional.empty()); assertThrows(ResourceNotFoundException.class, () -> - tenantService.getTenantDetailsById(consortiumId, tenantId)); + tenantService.getTenantDetailsById(consortiumId, TENANT_ID)); + } + + private void mockOkapiHeaders() { + when(folioExecutionContext.getTenantId()).thenReturn("diku"); + Map> okapiHeaders = createOkapiHeaders(); + when(folioExecutionContext.getOkapiHeaders()).thenReturn(okapiHeaders); } private static Object runSecondArgument(InvocationOnMock invocation) { diff --git a/src/test/java/org/folio/consortia/service/UserAffiliationServiceTest.java b/src/test/java/org/folio/consortia/service/UserAffiliationServiceTest.java index e3c39726..72f29cfc 100644 --- a/src/test/java/org/folio/consortia/service/UserAffiliationServiceTest.java +++ b/src/test/java/org/folio/consortia/service/UserAffiliationServiceTest.java @@ -1,9 +1,9 @@ package org.folio.consortia.service; +import static org.folio.consortia.support.EntityUtils.createOkapiHeaders; import static org.folio.consortia.support.EntityUtils.createTenantEntity; import static org.folio.consortia.utils.InputOutputTestUtils.getMockDataAsString; import static org.folio.spring.integration.XOkapiHeaders.TENANT; -import static org.folio.spring.integration.XOkapiHeaders.TOKEN; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -15,20 +15,14 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import java.util.UUID; import org.folio.consortia.config.kafka.KafkaService; +import org.folio.consortia.domain.dto.PrimaryAffiliationEvent; import org.folio.consortia.domain.entity.UserTenantEntity; import org.folio.consortia.repository.TenantRepository; import org.folio.consortia.service.impl.UserAffiliationServiceImpl; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import org.folio.consortia.domain.dto.PrimaryAffiliationEvent; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; -import org.folio.spring.integration.XOkapiHeaders; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -259,15 +253,6 @@ void deletePrimaryAffiliationNotParsed() { private void mockOkapiHeaders() { when(folioExecutionContext.getTenantId()).thenReturn("diku"); - Map> okapiHeaders = createOkapiHeaders(); - when(folioExecutionContext.getOkapiHeaders()).thenReturn(okapiHeaders); - } - - private Map> createOkapiHeaders() { - Map> map = new HashMap<>(); - map.put(TENANT, List.of(TENANT)); - map.put(TOKEN, List.of(TOKEN)); - map.put(XOkapiHeaders.USER_ID, List.of(UUID.randomUUID().toString())); - return map; + when(folioExecutionContext.getOkapiHeaders()).thenReturn(createOkapiHeaders()); } } diff --git a/src/test/java/org/folio/consortia/service/UserServiceTest.java b/src/test/java/org/folio/consortia/service/UserServiceTest.java index f28916bd..704f22cc 100644 --- a/src/test/java/org/folio/consortia/service/UserServiceTest.java +++ b/src/test/java/org/folio/consortia/service/UserServiceTest.java @@ -1,23 +1,24 @@ package org.folio.consortia.service; +import static org.folio.consortia.support.EntityUtils.createOkapiHeaders; import static org.folio.consortia.support.EntityUtils.createUserEntity; +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; -import org.folio.consortia.client.UsersKeycloakClient; -import org.folio.consortia.domain.dto.UserType; -import org.folio.consortia.exception.ResourceNotFoundException; -import org.folio.consortia.service.impl.UserServiceImpl; import java.util.Collection; -import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.UUID; - +import org.folio.consortia.client.UsersKeycloakClient; import org.folio.consortia.domain.dto.User; +import org.folio.consortia.domain.dto.UserType; +import org.folio.consortia.service.impl.UserServiceImpl; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; -import org.folio.spring.integration.XOkapiHeaders; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -45,39 +46,52 @@ void shouldCreateUser() { User user = createUserEntity(false); Mockito.doNothing().when(usersKeycloakClient).saveUser(user); User createdUser = userService.createUser(user); - Assertions.assertEquals(user, createdUser); + assertEquals(user, createdUser); } @Test void shouldUpdateUser() { User user = createUserEntity(false); - Mockito.doNothing().when(usersKeycloakClient).updateUser(user.getId(), user); - Assertions.assertDoesNotThrow(() -> userService.updateUser(user)); + doNothing().when(usersKeycloakClient).updateUser(user.getId(), user); + assertDoesNotThrow(() -> userService.updateUser(user)); } @Test void shouldDeleteUser() { User user = createUserEntity(false); - Mockito.doNothing().when(usersKeycloakClient).deleteUser(user.getId()); - Assertions.assertDoesNotThrow(() -> userService.deleteById(user.getId())); + doNothing().when(usersKeycloakClient).deleteUser(user.getId()); + assertDoesNotThrow(() -> userService.deleteById(user.getId())); } @Test void shouldThrowNotFoundWhilePrepareShadowUser() { - when(folioExecutionContext.getTenantId()).thenReturn("diku"); - Map> okapiHeaders = new HashMap<>(); - okapiHeaders.put(XOkapiHeaders.TENANT, List.of("diku")); - when(folioExecutionContext.getOkapiHeaders()).thenReturn(okapiHeaders); - Mockito.when(usersKeycloakClient.getUsersByUserId(any())).thenReturn(new User()); - Assertions.assertThrows(ResourceNotFoundException.class, () -> userService.prepareShadowUser(UUID.randomUUID(), "")); + mockOkapiHeaders(); + when(usersKeycloakClient.getUsersByUserId(any())).thenReturn(new User()); + + assertThrows(org.folio.consortia.exception.ResourceNotFoundException.class, + () -> userService.prepareShadowUser(UUID.randomUUID(), "")); } @Test void shouldPrepareShadowUser() { + when(usersKeycloakClient.getUsersByUserId(any())).thenReturn(createUserEntity(true)); + mockOkapiHeaders(); + + User shadow = userService.prepareShadowUser(UUID.randomUUID(), "diku"); + + assertEquals(UserType.SHADOW.getName(), shadow.getType()); + assertEquals("diku", shadow.getCustomFields().get("originaltenantid")); + assertEquals(true, shadow.getActive()); + assertEquals("testFirst", shadow.getPersonal().getFirstName()); + assertEquals("testLast", shadow.getPersonal().getLastName()); + assertEquals("Test@mail.com", shadow.getPersonal().getEmail()); + assertEquals("email", shadow.getPersonal().getPreferredContactTypeId()); + assertNull(shadow.getBarcode()); + } + + private void mockOkapiHeaders() { when(folioExecutionContext.getTenantId()).thenReturn("diku"); - when(folioExecutionContext.getFolioModuleMetadata()).thenReturn(folioModuleMetadata); - Map> okapiHeaders = new HashMap<>(); - okapiHeaders.put(XOkapiHeaders.TENANT, List.of("diku")); + Map> okapiHeaders = createOkapiHeaders(); when(folioExecutionContext.getOkapiHeaders()).thenReturn(okapiHeaders); Mockito.when(usersKeycloakClient.getUsersByUserId(any())).thenReturn(createUserEntity(true)); User user = userService.prepareShadowUser(UUID.randomUUID(), "diku"); @@ -87,5 +101,4 @@ void shouldPrepareShadowUser() { Assertions.assertEquals("testFirst", user.getPersonal().getFirstName()); Assertions.assertEquals("testLast", user.getPersonal().getLastName()); } - } diff --git a/src/test/java/org/folio/consortia/service/UserTenantServiceTest.java b/src/test/java/org/folio/consortia/service/UserTenantServiceTest.java index ab7b4527..b6ec59be 100644 --- a/src/test/java/org/folio/consortia/service/UserTenantServiceTest.java +++ b/src/test/java/org/folio/consortia/service/UserTenantServiceTest.java @@ -45,6 +45,7 @@ import org.folio.consortia.service.impl.UserTenantServiceImpl; import org.folio.spring.FolioExecutionContext; import org.folio.spring.FolioModuleMetadata; +import org.folio.spring.data.OffsetRequest; import org.folio.spring.integration.XOkapiHeaders; import org.folio.spring.service.SystemUserScopedExecutionService; import org.junit.jupiter.api.Assertions; @@ -62,7 +63,6 @@ import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; @SpringBootTest @EnableAutoConfiguration(exclude = BatchAutoConfiguration.class) @@ -103,10 +103,10 @@ class UserTenantServiceTest { void shouldGetUserTenantList() { // given List userTenantEntities = List.of(new UserTenantEntity(), new UserTenantEntity()); - Page userTenantPage = new PageImpl<>(userTenantEntities, PageRequest.of(0, 10), userTenantEntities.size()); + Page userTenantPage = new PageImpl<>(userTenantEntities, OffsetRequest.of(0, 10), userTenantEntities.size()); when(consortiumRepository.findById(UUID.fromString(CONSORTIUM_ID))).thenReturn(Optional.of(createConsortiumEntity())); - when(userTenantRepository.findAll(PageRequest.of(0, 10))).thenReturn(userTenantPage); + when(userTenantRepository.getAll(OffsetRequest.of(0, 10))).thenReturn(userTenantPage); // when var result = userTenantService.get(UUID.fromString(CONSORTIUM_ID), 0, 10); @@ -151,7 +151,8 @@ void shouldGetUserTenantListByUserId() { when(consortiumRepository.findById(UUID.fromString(CONSORTIUM_ID))).thenReturn(Optional.of(createConsortiumEntity())); when(conversionService.convert(userTenant, UserTenant.class)).thenReturn(toDto(userTenant)); when(conversionService.convert(userTenant2, UserTenant.class)).thenReturn(toDto(userTenant2)); - when(userTenantRepository.findByUserId(userId, PageRequest.of(0, 10))).thenReturn(new PageImpl<>(userTenantEntities, PageRequest.of(0, 10), userTenantEntities.size())); + when(userTenantRepository.findByUserId(userId, OffsetRequest.of(0, 10))) + .thenReturn(new PageImpl<>(userTenantEntities, OffsetRequest.of(0, 10), userTenantEntities.size())); // when UserTenantCollection result = userTenantService.getByUserId(UUID.fromString(CONSORTIUM_ID), userId, 0, 10); @@ -443,7 +444,7 @@ void shouldReturn404UserIdNotFoundException() { UUID userId = UUID.randomUUID(); when(consortiumRepository.findById(UUID.fromString(CONSORTIUM_ID))).thenReturn(Optional.of(createConsortiumEntity())); - when(userTenantRepository.findByUserId(userId, PageRequest.of(0, 10))).thenReturn(new PageImpl<>(new ArrayList<>())); + when(userTenantRepository.findByUserId(userId, OffsetRequest.of(0, 10))).thenReturn(new PageImpl<>(new ArrayList<>())); UUID id = UUID.fromString(CONSORTIUM_ID); // throw exception @@ -575,7 +576,8 @@ void shouldThrowIllegalStateExceptionFromUserClient(String username) { when(userService.getById(any())).thenThrow(java.lang.IllegalStateException.class); mockOkapiHeaders(); - assertThrows(java.lang.IllegalStateException.class, () -> userTenantService.save(UUID.fromString(CONSORTIUM_ID), tenant, false)); + assertThrows(java.lang.IllegalStateException.class, + () -> userTenantService.save(UUID.fromString(CONSORTIUM_ID), tenant, false)); } @Test 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 270d556f..5a7bf0a5 100644 --- a/src/test/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImplTest.java +++ b/src/test/java/org/folio/consortia/service/impl/SyncPrimaryAffiliationServiceImplTest.java @@ -94,7 +94,7 @@ void createPrimaryUserAffiliationsWhenCentralTenantSaving() throws JsonProcessin // stub collection of 2 users when(tenantService.getByTenantId(anyString())).thenReturn(tenantEntity1); - when(userTenantRepository.findByUserId(any(), any())).thenReturn(new PageImpl<>(Collections.emptyList())); + when(userTenantRepository.findAnyByUserId(any(), any())).thenReturn(new PageImpl<>(Collections.emptyList())); when(tenantRepository.findById(anyString())).thenReturn(Optional.of(tenantEntity1)); when(userService.getUsersByQuery(anyString(), anyInt(), anyInt())).thenReturn(userCollection); when(consortiaConfigurationService.getCentralTenantId(anyString())).thenReturn(tenantId); @@ -125,7 +125,7 @@ void createPrimaryUserAffiliationsWhenLocalTenantSaving() throws JsonProcessingE // stub collection of 2 users when(tenantService.getByTenantId(anyString())).thenReturn(tenantEntity1); - when(userTenantRepository.findByUserId(any(), any())).thenReturn(new PageImpl<>(Collections.emptyList())); + when(userTenantRepository.findAnyByUserId(any(), any())).thenReturn(new PageImpl<>(Collections.emptyList())); when(tenantRepository.findById(anyString())).thenReturn(Optional.of(tenantEntity1)); when(userService.getUsersByQuery(anyString(), anyInt(), anyInt())).thenReturn(userCollection); when(consortiaConfigurationService.getCentralTenantId(anyString())).thenReturn(centralTenantId); @@ -222,7 +222,7 @@ void createPrimaryAffiliationsException() { } @Test - void createPrimaryAffiliationsPartialFailure() throws JsonProcessingException { + void createPrimaryAffiliationsPartialFailure() { var consortiumId = UUID.randomUUID(); var tenantId = "ABC1"; var centralTenantId = "diku"; diff --git a/src/test/java/org/folio/consortia/support/EntityUtils.java b/src/test/java/org/folio/consortia/support/EntityUtils.java index bd25f793..2839f834 100644 --- a/src/test/java/org/folio/consortia/support/EntityUtils.java +++ b/src/test/java/org/folio/consortia/support/EntityUtils.java @@ -1,10 +1,14 @@ package org.folio.consortia.support; +import static org.folio.spring.integration.XOkapiHeaders.TENANT; +import static org.folio.spring.integration.XOkapiHeaders.TOKEN; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.time.LocalDateTime; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,10 +40,15 @@ 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.integration.XOkapiHeaders; import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; @UtilityClass public class EntityUtils { + public static final UUID ACTION_ID = UUID.fromString("dcfc317b-0d7c-4334-8656-596105fa6c99"); + public static final UUID INSTANCE_ID = UUID.fromString("111841e3-e6fb-4191-8fd8-5674a5107c33"); + public static final String CENTRAL_TENANT_ID = "consortium"; + public static final String TENANT_ID = "diku"; public static ConsortiumEntity createConsortiumEntity(String id, String name) { ConsortiumEntity consortiumEntity = new ConsortiumEntity(); @@ -62,6 +71,7 @@ public static TenantEntity createTenantEntity(String id, String name, String cod tenantEntity.setName(name); tenantEntity.setIsCentral(isCentral); tenantEntity.setConsortiumId(UUID.randomUUID()); + tenantEntity.setIsDeleted(false); return tenantEntity; } @@ -72,6 +82,7 @@ public static TenantEntity createTenantEntity() { tenantEntity.setName("testtenant1"); tenantEntity.setIsCentral(false); tenantEntity.setConsortiumId(UUID.randomUUID()); + tenantEntity.setIsDeleted(false); return tenantEntity; } @@ -81,6 +92,17 @@ public static TenantEntity createTenantEntity(String id, String name) { tenantEntity.setCode("ABC"); tenantEntity.setName(name); tenantEntity.setIsCentral(false); + tenantEntity.setIsDeleted(false); + return tenantEntity; + } + + public static TenantEntity createTenantEntity(String id) { + TenantEntity tenantEntity = new TenantEntity(); + tenantEntity.setId(id); + tenantEntity.setCode("ABC"); + tenantEntity.setName(id); + tenantEntity.setIsCentral(false); + tenantEntity.setIsDeleted(false); return tenantEntity; } @@ -90,6 +112,7 @@ public static TenantDetailsEntity createTenantDetailsEntity() { tenantDetailsEntity.setCode("ABC"); tenantDetailsEntity.setName("testtenant1"); tenantDetailsEntity.setIsCentral(false); + tenantDetailsEntity.setIsDeleted(false); tenantDetailsEntity.setConsortiumId(UUID.randomUUID()); tenantDetailsEntity.setSetupStatus(SetupStatusEnum.COMPLETED); return tenantDetailsEntity; @@ -101,6 +124,7 @@ public static TenantDetailsEntity createTenantDetailsEntity(String id, String na tenantDetailsEntity.setCode("ABC"); tenantDetailsEntity.setName(name); tenantDetailsEntity.setIsCentral(false); + tenantDetailsEntity.setIsDeleted(false); tenantDetailsEntity.setSetupStatus(SetupStatusEnum.IN_PROGRESS); return tenantDetailsEntity; } @@ -111,6 +135,7 @@ public static Tenant createTenant(String id, String name) { tenant.setName(name); tenant.setIsCentral(false); tenant.setCode("ABC"); + tenant.setIsDeleted(false); return tenant; } @@ -119,6 +144,7 @@ public static Tenant createTenant(String id, String name, boolean isCentral) { tenant.setId(id); tenant.setName(name); tenant.setIsCentral(isCentral); + tenant.setIsDeleted(false); tenant.setCode("ABC"); return tenant; } @@ -297,6 +323,10 @@ public static User createUser(String username) { return new User().id(UUID.randomUUID().toString()).username(username); } + public static User createUser(UUID id, String username) { + return new User().id(id.toString()).username(username); + } + public static User createUserEntity(Boolean updateble) { User user = new User(); Personal personal = new Personal(); @@ -309,6 +339,7 @@ public static User createUserEntity(Boolean updateble) { user.setUsername("xyz"); user.setPersonal(personal); user.setActive(Boolean.FALSE.equals(updateble)); + user.setBarcode("0420690"); return user; } @@ -326,4 +357,12 @@ public static User createUserEntity(UUID userId) { user.setActive(true); return user; } + + public static Map> createOkapiHeaders() { + Map> map = new HashMap<>(); + map.put(TENANT, List.of("diku")); + map.put(TOKEN, List.of(TOKEN)); + map.put(XOkapiHeaders.USER_ID, List.of(UUID.randomUUID().toString())); + return map; + } } diff --git a/src/test/java/org/folio/consortia/support/extension/impl/OkapiExtension.java b/src/test/java/org/folio/consortia/support/extension/impl/OkapiExtension.java index 9c45a64d..0ed1747f 100644 --- a/src/test/java/org/folio/consortia/support/extension/impl/OkapiExtension.java +++ b/src/test/java/org/folio/consortia/support/extension/impl/OkapiExtension.java @@ -8,10 +8,13 @@ import java.io.IOException; import java.net.ServerSocket; +import java.util.ArrayList; import java.util.PrimitiveIterator; import java.util.concurrent.ThreadLocalRandom; +import com.github.tomakehurst.wiremock.common.ClasspathFileSource; import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -24,7 +27,7 @@ public class OkapiExtension implements BeforeAllCallback, AfterAllCallback { private static final WireMockServer WIRE_MOCK = new WireMockServer( wireMockConfig() .port(nextFreePort()) - .extensions(new ResponseTemplateTransformer(true)) + .extensions(new ResponseTemplateTransformer(TemplateEngine.defaultTemplateEngine(), true, new ClasspathFileSource("/"), new ArrayList<>())) ); private static int nextFreePort() {