diff --git a/NEWS.md b/NEWS.md
index c25416fd..8e13c0d3 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,7 @@
+## 1.0.3 2024-12-30
+* DCB transaction status synchronization (MODTLR-112)
+* Resolve central tenant ID dynamically (MODTLR-118)
+*
## 1.0.2 2024-12-12
* Copy Secure Patron name when cloning users (MODTLR-116)
* Support for intermediate requests (MODTLR-98)
diff --git a/pom.xml b/pom.xml
index 00af855e..e1b7720c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
org.folio
mod-tlr
mod-tlr
- 1.0.3-SNAPSHOT
+ 1.0.4-SNAPSHOT
FOLIO mod-tlr module
jar
diff --git a/src/main/java/org/folio/client/feign/ConsortiaConfigurationClient.java b/src/main/java/org/folio/client/feign/ConsortiaConfigurationClient.java
new file mode 100644
index 00000000..d9602ae5
--- /dev/null
+++ b/src/main/java/org/folio/client/feign/ConsortiaConfigurationClient.java
@@ -0,0 +1,13 @@
+package org.folio.client.feign;
+
+import org.folio.domain.dto.ConsortiaConfiguration;
+import org.folio.spring.config.FeignClientConfiguration;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@FeignClient(name = "consortia-configuration", url = "consortia-configuration", configuration = FeignClientConfiguration.class)
+public interface ConsortiaConfigurationClient {
+
+ @GetMapping
+ ConsortiaConfiguration getConfiguration();
+}
diff --git a/src/main/java/org/folio/domain/Constants.java b/src/main/java/org/folio/domain/Constants.java
deleted file mode 100644
index 35a1ad8e..00000000
--- a/src/main/java/org/folio/domain/Constants.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.folio.domain;
-
-import static org.folio.domain.dto.Request.RequestTypeEnum.HOLD;
-
-import org.folio.domain.dto.Request;
-
-import lombok.experimental.UtilityClass;
-
-@UtilityClass
-public class Constants {
- public static final String CENTRAL_TENANT_ID = "consortium";
- public static final Request.RequestTypeEnum PRIMARY_REQUEST_TYPE = HOLD;
-}
diff --git a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java
index f6e7b840..bc87dfed 100644
--- a/src/main/java/org/folio/listener/kafka/KafkaEventListener.java
+++ b/src/main/java/org/folio/listener/kafka/KafkaEventListener.java
@@ -1,114 +1,115 @@
package org.folio.listener.kafka;
-import static org.folio.domain.Constants.CENTRAL_TENANT_ID;
-
import java.nio.charset.StandardCharsets;
+import java.util.Map;
import java.util.Optional;
+import org.folio.domain.dto.Loan;
import org.folio.domain.dto.Request;
import org.folio.domain.dto.RequestsBatchUpdate;
import org.folio.domain.dto.User;
import org.folio.domain.dto.UserGroup;
import org.folio.exception.KafkaEventDeserializationException;
+import org.folio.service.ConsortiaService;
import org.folio.service.KafkaEventHandler;
+import org.folio.service.impl.LoanEventHandler;
import org.folio.service.impl.RequestBatchUpdateEventHandler;
import org.folio.service.impl.RequestEventHandler;
import org.folio.service.impl.UserEventHandler;
import org.folio.service.impl.UserGroupEventHandler;
+import org.folio.spring.DefaultFolioExecutionContext;
+import org.folio.spring.FolioExecutionContext;
+import org.folio.spring.FolioModuleMetadata;
import org.folio.spring.integration.XOkapiHeaders;
+import org.folio.spring.scope.FolioExecutionContextSetter;
import org.folio.spring.service.SystemUserScopedExecutionService;
import org.folio.support.KafkaEvent;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
-import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Component
@Log4j2
+@RequiredArgsConstructor
public class KafkaEventListener {
private static final ObjectMapper objectMapper = new ObjectMapper();
private final RequestEventHandler requestEventHandler;
+ private final LoanEventHandler loanEventHandler;
private final UserGroupEventHandler userGroupEventHandler;
private final UserEventHandler userEventHandler;
private final SystemUserScopedExecutionService systemUserScopedExecutionService;
private final RequestBatchUpdateEventHandler requestBatchEventHandler;
-
- public KafkaEventListener(@Autowired RequestEventHandler requestEventHandler,
- @Autowired RequestBatchUpdateEventHandler requestBatchEventHandler,
- @Autowired SystemUserScopedExecutionService systemUserScopedExecutionService,
- @Autowired UserGroupEventHandler userGroupEventHandler,
- @Autowired UserEventHandler userEventHandler) {
-
- this.requestEventHandler = requestEventHandler;
- this.systemUserScopedExecutionService = systemUserScopedExecutionService;
- this.userGroupEventHandler = userGroupEventHandler;
- this.requestBatchEventHandler = requestBatchEventHandler;
- this.userEventHandler = userEventHandler;
- }
+ private final ConsortiaService consortiaService;
+ private final FolioModuleMetadata folioModuleMetadata;
@KafkaListener(
topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.request",
groupId = "${spring.kafka.consumer.group-id}"
)
- public void handleRequestEvent(String eventString, MessageHeaders messageHeaders) {
- log.debug("handleRequestEvent:: event: {}", () -> eventString);
- KafkaEvent event = deserialize(eventString, messageHeaders, Request.class);
- log.info("handleRequestEvent:: event received: {}", event::getId);
- handleEvent(event, requestEventHandler);
- log.info("handleRequestEvent:: event consumed: {}", event::getId);
+ public void handleRequestEvent(String eventString, @Headers Map messageHeaders) {
+ handleEvent(eventString, requestEventHandler, messageHeaders, Request.class);
}
@KafkaListener(
- topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.request-queue-reordering",
+ topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.loan",
groupId = "${spring.kafka.consumer.group-id}"
)
- public void handleRequestBatchUpdateEvent(String eventString, MessageHeaders messageHeaders) {
- log.debug("handleRequestBatchUpdateEvent:: event: {}", () -> eventString);
- KafkaEvent event = deserialize(eventString, messageHeaders, RequestsBatchUpdate.class);
- log.info("handleRequestBatchUpdateEvent:: event received: {}", event::getId);
- handleEvent(event, requestBatchEventHandler);
- log.info("handleRequestBatchUpdateEvent:: event consumed: {}", event::getId);
+ public void handleLoanEvent(String eventString, @Headers Map messageHeaders) {
+ handleEvent(eventString, loanEventHandler, messageHeaders, Loan.class);
}
- private void handleEvent(KafkaEvent event, KafkaEventHandler handler) {
- try {
- systemUserScopedExecutionService.executeAsyncSystemUserScoped(CENTRAL_TENANT_ID,
- () -> handler.handle(event));
- } catch (Exception e) {
- log.error("handleEvent:: Failed to handle Kafka event in tenant {}", CENTRAL_TENANT_ID);
- }
+ @KafkaListener(
+ topicPattern = "${folio.environment}\\.\\w+\\.circulation\\.request-queue-reordering",
+ groupId = "${spring.kafka.consumer.group-id}"
+ )
+ public void handleRequestBatchUpdateEvent(String eventString, @Headers Map messageHeaders) {
+ handleEvent(eventString, requestBatchEventHandler, messageHeaders, RequestsBatchUpdate.class);
}
@KafkaListener(
topicPattern = "${folio.environment}\\.\\w+\\.users\\.userGroup",
groupId = "${spring.kafka.consumer.group-id}"
)
- public void handleUserGroupEvent(String eventString, MessageHeaders messageHeaders) {
- KafkaEvent event = deserialize(eventString, messageHeaders, UserGroup.class);
-
- log.info("handleUserGroupEvent:: event received: {}", event::getId);
- log.debug("handleUserGroupEvent:: event: {}", () -> event);
- handleEvent(event, userGroupEventHandler);
+ public void handleUserGroupEvent(String eventString, @Headers Map messageHeaders) {
+ handleEvent(eventString, userGroupEventHandler, messageHeaders, UserGroup.class);
}
@KafkaListener(
topicPattern = "${folio.environment}\\.\\w+\\.users\\.users",
groupId = "${spring.kafka.consumer.group-id}"
)
- public void handleUserEvent(String eventString, MessageHeaders messageHeaders) {
- KafkaEvent event = deserialize(eventString, messageHeaders, User.class);
+ public void handleUserEvent(String eventString, @Headers Map messageHeaders) {
+ handleEvent(eventString, userEventHandler, messageHeaders, User.class);
+ }
+
+ private void handleEvent(String eventString, KafkaEventHandler handler,
+ Map messageHeaders, Class payloadType) {
- log.info("handleUserEvent:: event received: {}", event::getId);
- handleEvent(event, userEventHandler);
+ log.debug("handleEvent:: event: {}", () -> eventString);
+ KafkaEvent event = deserialize(eventString, messageHeaders, payloadType);
+ log.info("handleEvent:: event received: {}", event::getId);
+
+ FolioExecutionContext context = DefaultFolioExecutionContext.fromMessageHeaders(
+ folioModuleMetadata, messageHeaders);
+
+ try (FolioExecutionContextSetter contextSetter = new FolioExecutionContextSetter(context)) {
+ String centralTenantId = consortiaService.getCentralTenantId();
+ systemUserScopedExecutionService.executeAsyncSystemUserScoped(centralTenantId,
+ () -> handler.handle(event));
+ } catch (Exception e) {
+ log.error("handleEvent:: failed to handle event {}", event.getId(), e);
+ }
+ log.info("handleEvent:: event consumed: {}", event::getId);
}
- private static KafkaEvent deserialize(String eventString, MessageHeaders messageHeaders,
+ private static KafkaEvent deserialize(String eventString, Map messageHeaders,
Class dataType) {
try {
@@ -125,7 +126,7 @@ private static KafkaEvent deserialize(String eventString, MessageHeaders
}
}
- private static String getHeaderValue(MessageHeaders headers, String headerName) {
+ private static String getHeaderValue(Map headers, String headerName) {
log.debug("getHeaderValue:: headers: {}, headerName: {}", () -> headers, () -> headerName);
var headerValue = headers.get(headerName);
var value = headerValue == null
diff --git a/src/main/java/org/folio/repository/EcsTlrRepository.java b/src/main/java/org/folio/repository/EcsTlrRepository.java
index 1679554a..4c45fde6 100644
--- a/src/main/java/org/folio/repository/EcsTlrRepository.java
+++ b/src/main/java/org/folio/repository/EcsTlrRepository.java
@@ -14,4 +14,5 @@ public interface EcsTlrRepository extends JpaRepository {
Optional findByPrimaryRequestId(UUID primaryRequestId);
Optional findByInstanceId(UUID instanceId);
List findByPrimaryRequestIdIn(List primaryRequestIds);
+ List findByItemId(UUID itemId);
}
diff --git a/src/main/java/org/folio/service/ConsortiaService.java b/src/main/java/org/folio/service/ConsortiaService.java
index 562d9749..f676f228 100644
--- a/src/main/java/org/folio/service/ConsortiaService.java
+++ b/src/main/java/org/folio/service/ConsortiaService.java
@@ -8,4 +8,5 @@
public interface ConsortiaService {
TenantCollection getAllConsortiumTenants(String consortiumId);
Collection getAllConsortiumTenants();
+ String getCentralTenantId();
}
diff --git a/src/main/java/org/folio/service/DcbService.java b/src/main/java/org/folio/service/DcbService.java
index 7b8cb372..5bf75405 100644
--- a/src/main/java/org/folio/service/DcbService.java
+++ b/src/main/java/org/folio/service/DcbService.java
@@ -13,7 +13,8 @@ public interface DcbService {
void createBorrowerTransaction(EcsTlrEntity ecsTlr, Request request);
void createBorrowingPickupTransaction(EcsTlrEntity ecsTlr, Request request);
void createPickupTransaction(EcsTlrEntity ecsTlr, Request request);
+ void updateTransactionStatuses(TransactionStatus.StatusEnum newStatus, EcsTlrEntity ecsTlr);
TransactionStatusResponse getTransactionStatus(UUID transactionId, String tenantId);
- TransactionStatusResponse updateTransactionStatus(UUID transactionId,
- TransactionStatus.StatusEnum newStatus, String tenantId);
+ void updateTransactionStatus(UUID transactionId, TransactionStatus.StatusEnum newStatus,
+ String tenantId);
}
diff --git a/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java b/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java
index e328de0b..cbc145ef 100644
--- a/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java
+++ b/src/main/java/org/folio/service/impl/ConsortiaServiceImpl.java
@@ -6,6 +6,8 @@
import java.util.Optional;
import org.folio.client.feign.ConsortiaClient;
+import org.folio.client.feign.ConsortiaConfigurationClient;
+import org.folio.domain.dto.ConsortiaConfiguration;
import org.folio.domain.dto.Tenant;
import org.folio.domain.dto.TenantCollection;
import org.folio.domain.dto.UserTenant;
@@ -21,6 +23,7 @@
@RequiredArgsConstructor
public class ConsortiaServiceImpl implements ConsortiaService {
private final ConsortiaClient consortiaClient;
+ private final ConsortiaConfigurationClient consortiaConfigurationClient;
private final UserTenantsService userTenantsService;
@Override
@@ -40,4 +43,14 @@ public Collection getAllConsortiumTenants() {
log.info("getAllConsortiumTenants:: found {} consortium tenants", tenants::size);
return tenants;
}
+
+ @Override
+ public String getCentralTenantId() {
+ log.info("getCentralTenantId:: resolving central tenant ID");
+ String centralTenantId = Optional.ofNullable(consortiaConfigurationClient.getConfiguration())
+ .map(ConsortiaConfiguration::getCentralTenantId)
+ .orElseThrow();
+ log.info("getCentralTenantId:: central tenant ID: {}", centralTenantId);
+ return centralTenantId;
+ }
}
diff --git a/src/main/java/org/folio/service/impl/DcbServiceImpl.java b/src/main/java/org/folio/service/impl/DcbServiceImpl.java
index 99017e46..3408e23f 100644
--- a/src/main/java/org/folio/service/impl/DcbServiceImpl.java
+++ b/src/main/java/org/folio/service/impl/DcbServiceImpl.java
@@ -4,6 +4,13 @@
import static org.folio.domain.dto.DcbTransaction.RoleEnum.BORROWING_PICKUP;
import static org.folio.domain.dto.DcbTransaction.RoleEnum.LENDER;
import static org.folio.domain.dto.DcbTransaction.RoleEnum.PICKUP;
+import static org.folio.domain.dto.TransactionStatus.StatusEnum.AWAITING_PICKUP;
+import static org.folio.domain.dto.TransactionStatus.StatusEnum.CANCELLED;
+import static org.folio.domain.dto.TransactionStatus.StatusEnum.CLOSED;
+import static org.folio.domain.dto.TransactionStatus.StatusEnum.CREATED;
+import static org.folio.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_IN;
+import static org.folio.domain.dto.TransactionStatus.StatusEnum.ITEM_CHECKED_OUT;
+import static org.folio.domain.dto.TransactionStatus.StatusEnum.OPEN;
import java.util.UUID;
@@ -14,6 +21,7 @@
import org.folio.domain.dto.DcbTransaction.RoleEnum;
import org.folio.domain.dto.Request;
import org.folio.domain.dto.TransactionStatus;
+import org.folio.domain.dto.TransactionStatus.StatusEnum;
import org.folio.domain.dto.TransactionStatusResponse;
import org.folio.domain.entity.EcsTlrEntity;
import org.folio.service.DcbService;
@@ -21,6 +29,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import feign.FeignException;
import lombok.extern.log4j.Log4j2;
@Service
@@ -114,18 +123,6 @@ public TransactionStatusResponse getTransactionStatus(UUID transactionId, String
() -> dcbTransactionClient.getDcbTransactionStatus(transactionId.toString()));
}
- @Override
- public TransactionStatusResponse updateTransactionStatus(UUID transactionId,
- TransactionStatus.StatusEnum newStatus, String tenantId) {
-
- log.info("updateTransactionStatus:: transactionId={}, newStatus={}, tenantId={}",
- transactionId, newStatus, tenantId);
-
- return executionService.executeSystemUserScoped(tenantId,
- () -> dcbTransactionClient.changeDcbTransactionStatus(
- transactionId.toString(), new TransactionStatus().status(newStatus)));
- }
-
@Override
public void createTransactions(EcsTlrEntity ecsTlr, Request secondaryRequest) {
log.info("createTransactions:: creating transactions for ECS TLR {}", ecsTlr::getId);
@@ -143,4 +140,89 @@ public void createTransactions(EcsTlrEntity ecsTlr, Request secondaryRequest) {
}
}
+ @Override
+ public void updateTransactionStatuses(TransactionStatus.StatusEnum newStatus, EcsTlrEntity ecsTlr) {
+ log.info("updateTransactionStatuses:: updating primary transaction status to {}", newStatus::getValue);
+ updateTransactionStatus(ecsTlr.getPrimaryRequestDcbTransactionId(), newStatus,
+ ecsTlr.getPrimaryRequestTenantId());
+
+ log.info("updateTransactionStatuses:: updating intermediate transaction status to {}", newStatus::getValue);
+ updateTransactionStatus(ecsTlr.getIntermediateRequestDcbTransactionId(), newStatus,
+ ecsTlr.getIntermediateRequestTenantId());
+
+ log.info("updateTransactionStatuses:: updating secondary transaction status to {}", newStatus::getValue);
+ updateTransactionStatus(ecsTlr.getSecondaryRequestDcbTransactionId(), newStatus,
+ ecsTlr.getSecondaryRequestTenantId());
+ }
+
+ @Override
+ public void updateTransactionStatus(UUID transactionId, StatusEnum newStatus, String tenantId) {
+ if (transactionId == null) {
+ log.info("updateTransactionStatus:: transaction ID is null, doing nothing");
+ return;
+ }
+ if (tenantId == null) {
+ log.info("updateTransactionStatus:: tenant ID is null, doing nothing");
+ return;
+ }
+
+ try {
+ if (isTransactionStatusChangeAllowed(transactionId, newStatus, tenantId)) {
+ log.info("updateTransactionStatus: changing status of transaction {} in tenant {} to {}",
+ transactionId, tenantId, newStatus.getValue());
+
+ executionService.executeSystemUserScoped(tenantId,
+ () -> dcbTransactionClient.changeDcbTransactionStatus(transactionId.toString(),
+ new TransactionStatus().status(newStatus)));
+ }
+ } catch (FeignException.NotFound e) {
+ log.error("updateTransactionStatus:: transaction {} not found: {}", transactionId, e.getMessage());
+ } catch (Exception e) {
+ log.error("updateTransactionStatus:: failed to update transaction status: {}", e::getMessage);
+ log.debug("updateTransactionStatus:: ", e);
+ }
+ }
+
+ private boolean isTransactionStatusChangeAllowed(UUID transactionId, StatusEnum newStatus,
+ String tenantId) {
+
+ TransactionStatusResponse transaction = getTransactionStatus(transactionId, tenantId);
+ RoleEnum transactionRole = RoleEnum.fromValue(transaction.getRole().getValue());
+ StatusEnum currentStatus = StatusEnum.fromValue(transaction.getStatus().getValue());
+
+ return isTransactionStatusChangeAllowed(transactionRole, currentStatus, newStatus);
+ }
+
+ private static boolean isTransactionStatusChangeAllowed(RoleEnum role, StatusEnum oldStatus,
+ StatusEnum newStatus) {
+
+ log.info("isTransactionStatusChangeAllowed:: role={}, oldStatus={}, newStatus={}", role,
+ oldStatus, newStatus);
+
+ boolean isStatusChangeAllowed = false;
+
+ if (role == LENDER) {
+ isStatusChangeAllowed = (oldStatus == CREATED && newStatus == OPEN) ||
+ (oldStatus == OPEN && newStatus == AWAITING_PICKUP) ||
+ (oldStatus == AWAITING_PICKUP && newStatus == ITEM_CHECKED_OUT) ||
+ (oldStatus == ITEM_CHECKED_OUT && newStatus == ITEM_CHECKED_IN) ||
+ (oldStatus != CANCELLED && newStatus == CANCELLED);
+ }
+ else if (role == BORROWER) {
+ isStatusChangeAllowed = (oldStatus == CREATED && newStatus == OPEN) ||
+ (oldStatus == OPEN && newStatus == AWAITING_PICKUP) ||
+ (oldStatus == AWAITING_PICKUP && newStatus == ITEM_CHECKED_OUT) ||
+ (oldStatus == ITEM_CHECKED_OUT && newStatus == ITEM_CHECKED_IN) ||
+ (oldStatus == ITEM_CHECKED_IN && newStatus == CLOSED) ||
+ (oldStatus != CANCELLED && newStatus == CANCELLED);
+ }
+ else if (role == BORROWING_PICKUP || role == PICKUP) {
+ isStatusChangeAllowed = (oldStatus == CREATED && newStatus == OPEN) ||
+ (oldStatus == ITEM_CHECKED_IN && newStatus == CLOSED) ||
+ (oldStatus != CANCELLED && newStatus == CANCELLED);
+ }
+ log.info("isTransactionStatusChangeAllowed:: status change is allowed: {}", isStatusChangeAllowed);
+ return isStatusChangeAllowed;
+ }
+
}
diff --git a/src/main/java/org/folio/service/impl/LoanEventHandler.java b/src/main/java/org/folio/service/impl/LoanEventHandler.java
new file mode 100644
index 00000000..2f996126
--- /dev/null
+++ b/src/main/java/org/folio/service/impl/LoanEventHandler.java
@@ -0,0 +1,146 @@
+package org.folio.service.impl;
+
+import static org.folio.domain.dto.TransactionStatusResponse.RoleEnum.BORROWING_PICKUP;
+import static org.folio.domain.dto.TransactionStatusResponse.RoleEnum.LENDER;
+import static org.folio.domain.dto.TransactionStatusResponse.RoleEnum.PICKUP;
+import static org.folio.domain.dto.TransactionStatusResponse.StatusEnum.CLOSED;
+import static org.folio.domain.dto.TransactionStatusResponse.StatusEnum.ITEM_CHECKED_IN;
+import static org.folio.domain.dto.TransactionStatusResponse.StatusEnum.ITEM_CHECKED_OUT;
+import static org.folio.support.KafkaEvent.EventType.UPDATED;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.UUID;
+
+import org.folio.domain.dto.Loan;
+import org.folio.domain.dto.TransactionStatus.StatusEnum;
+import org.folio.domain.dto.TransactionStatusResponse;
+import org.folio.domain.dto.TransactionStatusResponse.RoleEnum;
+import org.folio.domain.entity.EcsTlrEntity;
+import org.folio.repository.EcsTlrRepository;
+import org.folio.service.DcbService;
+import org.folio.service.KafkaEventHandler;
+import org.folio.support.KafkaEvent;
+import org.springframework.stereotype.Service;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@AllArgsConstructor
+@Service
+@Log4j2
+public class LoanEventHandler implements KafkaEventHandler {
+ private static final String LOAN_ACTION_CHECKED_IN = "checkedin";
+ private static final EnumSet
+ RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN = EnumSet.of(ITEM_CHECKED_OUT, ITEM_CHECKED_IN, CLOSED);
+
+ private final DcbService dcbService;
+ private final EcsTlrRepository ecsTlrRepository;
+
+ @Override
+ public void handle(KafkaEvent event) {
+ log.info("handle:: processing loan event: {}", event::getId);
+ if (event.getType() == UPDATED) {
+ handleUpdateEvent(event);
+ } else {
+ log.info("handle:: ignoring event {} of unsupported type: {}", event::getId, event::getType);
+ }
+ log.info("handle:: loan event processed: {}", event::getId);
+ }
+
+ private void handleUpdateEvent(KafkaEvent event) {
+ Loan loan = event.getData().getNewVersion();
+ String loanAction = loan.getAction();
+ log.info("handle:: loan action: {}", loanAction);
+ if (LOAN_ACTION_CHECKED_IN.equals(loanAction)) {
+ log.info("handleUpdateEvent:: processing loan check-in event: {}", event::getId);
+ handleCheckInEvent(event);
+ } else {
+ log.info("handleUpdateEvent:: ignoring loan update event with unsupported loan action: {}", loanAction);
+ }
+ }
+
+ private void handleCheckInEvent(KafkaEvent event) {
+ updateEcsTlr(event.getData().getNewVersion(), event.getTenant());
+ }
+
+ private void updateEcsTlr(Loan loan, String tenantId) {
+ Collection ecsTlrs = findEcsTlrs(loan);
+ for (EcsTlrEntity ecsTlr : ecsTlrs) {
+ log.info("updateEcsTlr:: checking ECS TLR {}", ecsTlr::getId);
+ String primaryTenantId = ecsTlr.getPrimaryRequestTenantId();
+ String secondaryTenantId = ecsTlr.getSecondaryRequestTenantId();
+ UUID primaryTransactionId = ecsTlr.getPrimaryRequestDcbTransactionId();
+ UUID secondaryTransactionId = ecsTlr.getSecondaryRequestDcbTransactionId();
+
+ if (primaryTransactionId == null || secondaryTransactionId == null) {
+ log.info("updateEcsTlr:: ECS TLR does not have primary/secondary transaction, skipping");
+ continue;
+ }
+
+ boolean eventTenantIdIsPrimaryTenantId = tenantId.equals(primaryTenantId);
+ boolean eventTenantIdIsSecondaryTenantId = tenantId.equals(secondaryTenantId);
+ if (!(eventTenantIdIsPrimaryTenantId || eventTenantIdIsSecondaryTenantId)) {
+ log.info("updateEcsTlr:: event tenant ID does not match ECS TLR's primary/secondary request " +
+ "tenant ID, skipping");
+ continue;
+ }
+
+ TransactionStatusResponse primaryTransaction = dcbService.getTransactionStatus(
+ primaryTransactionId, primaryTenantId);
+ TransactionStatusResponse.StatusEnum primaryTransactionStatus = primaryTransaction.getStatus();
+ RoleEnum primaryTransactionRole = primaryTransaction.getRole();
+ log.info("updateEcsTlr:: primary request transaction: status={}, role={}",
+ primaryTransactionStatus, primaryTransactionRole);
+ if (!RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN.contains(primaryTransactionStatus)) {
+ log.info("updateEcsTlrForLoan:: irrelevant primary request transaction status: {}",
+ primaryTransaction);
+ continue;
+ }
+
+ TransactionStatusResponse secondaryTransaction = dcbService.getTransactionStatus(
+ secondaryTransactionId, secondaryTenantId);
+ TransactionStatusResponse.StatusEnum secondaryTransactionStatus = secondaryTransaction.getStatus();
+ RoleEnum secondaryTransactionRole = secondaryTransaction.getRole();
+ log.info("updateEcsTlr:: secondary request transaction: status={}, role={}",
+ secondaryTransactionStatus, secondaryTransactionRole);
+ if (!RELEVANT_TRANSACTION_STATUSES_FOR_CHECK_IN.contains(secondaryTransactionStatus)) {
+ log.info("updateEcsTlr:: irrelevant secondary request transaction status: {}",
+ secondaryTransactionStatus);
+ continue;
+ }
+
+ if (eventTenantIdIsPrimaryTenantId &&
+ (primaryTransactionRole == BORROWING_PICKUP || primaryTransactionRole == PICKUP) &&
+ (primaryTransactionStatus == ITEM_CHECKED_OUT || primaryTransactionStatus == ITEM_CHECKED_IN) &&
+ secondaryTransactionRole == LENDER && secondaryTransactionStatus == ITEM_CHECKED_OUT) {
+
+ log.info("updateEcsTlr:: check-in happened in primary request tenant ({}), updating transactions",
+ primaryTenantId);
+ dcbService.updateTransactionStatuses(StatusEnum.ITEM_CHECKED_IN, ecsTlr);
+ return;
+ }
+ else if (eventTenantIdIsSecondaryTenantId && secondaryTransactionRole == LENDER &&
+ (secondaryTransactionStatus == ITEM_CHECKED_IN || secondaryTransactionStatus == CLOSED) &&
+ (primaryTransactionRole == BORROWING_PICKUP || primaryTransactionRole == PICKUP) &&
+ primaryTransactionStatus == ITEM_CHECKED_IN) {
+
+ log.info("updateEcsTlr:: check-in happened in secondary request tenant ({}), updating transactions", secondaryTenantId);
+ dcbService.updateTransactionStatuses(StatusEnum.CLOSED, ecsTlr);
+ return;
+ }
+ log.info("updateEcsTlr:: ECS TLR {} does not match loan update event, skipping", ecsTlr::getId);
+ }
+ log.info("updateEcsTlr:: suitable ECS TLR for loan {} in tenant {} was not found", loan.getId(), tenantId);
+ }
+
+ private Collection findEcsTlrs(Loan loan) {
+ log.info("findEcsTlrs:: searching ECS TLRs for item {}", loan::getItemId);
+ List ecsTlrs = ecsTlrRepository.findByItemId(UUID.fromString(loan.getItemId()));
+ log.info("findEcsTlrs:: found {} ECS TLRs", ecsTlrs::size);
+
+ return ecsTlrs;
+ }
+
+}
diff --git a/src/main/java/org/folio/service/impl/RequestEventHandler.java b/src/main/java/org/folio/service/impl/RequestEventHandler.java
index f8087ace..e367cc41 100644
--- a/src/main/java/org/folio/service/impl/RequestEventHandler.java
+++ b/src/main/java/org/folio/service/impl/RequestEventHandler.java
@@ -30,7 +30,6 @@
import org.folio.support.KafkaEvent;
import org.springframework.stereotype.Service;
-import feign.FeignException;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
@@ -149,7 +148,7 @@ private static Optional determineNewTransactionSta
oldRequestStatus, newRequestStatus);
if (newRequestStatus == oldRequestStatus) {
- log.info("determineNewTransactionStatus:: request status did not change");
+ log.info("determineNewTransactionStatus:: request status did not change, doing nothing");
return Optional.empty();
}
@@ -171,51 +170,7 @@ private static Optional determineNewTransactionSta
private void updateTransactionStatuses(KafkaEvent event, EcsTlrEntity ecsTlr) {
determineNewTransactionStatus(event)
- .ifPresent(newStatus -> updateTransactionStatuses(newStatus, ecsTlr));
- }
-
- private void updateTransactionStatuses(TransactionStatus.StatusEnum newStatus, EcsTlrEntity ecsTlr) {
- log.info("updateTransactionStatuses:: updating primary transaction status to {}", newStatus::getValue);
- updateTransactionStatus(ecsTlr.getPrimaryRequestDcbTransactionId(), newStatus,
- ecsTlr.getPrimaryRequestTenantId());
-
- log.info("updateTransactionStatuses:: updating intermediate transaction status to {}", newStatus::getValue);
- updateTransactionStatus(ecsTlr.getIntermediateRequestDcbTransactionId(), newStatus,
- ecsTlr.getIntermediateRequestTenantId());
-
- log.info("updateTransactionStatuses:: updating secondary transaction status to {}", newStatus::getValue);
- updateTransactionStatus(ecsTlr.getSecondaryRequestDcbTransactionId(), newStatus,
- ecsTlr.getSecondaryRequestTenantId());
- }
-
- private void updateTransactionStatus(UUID transactionId,
- TransactionStatus.StatusEnum newStatus, String tenantId) {
-
- if (transactionId == null) {
- log.info("updateTransactionStatus:: transaction ID is null, doing nothing");
- return;
- }
- if (tenantId == null) {
- log.info("updateTransactionStatus:: tenant ID is null, doing nothing");
- return;
- }
-
- try {
- var currentStatus = dcbService.getTransactionStatus(transactionId, tenantId).getStatus();
- log.info("updateTransactionStatus:: current transaction status: {}", currentStatus);
- if (newStatus.getValue().equals(currentStatus.getValue())) {
- log.info("updateTransactionStatus:: transaction status did not change, doing nothing");
- return;
- }
- log.info("updateTransactionStatus: changing status of transaction {} in tenant {} from {} to {}",
- transactionId, tenantId, currentStatus.getValue(), newStatus.getValue());
- dcbService.updateTransactionStatus(transactionId, newStatus, tenantId);
- } catch (FeignException.NotFound e) {
- log.error("updateTransactionStatus:: transaction {} not found: {}", transactionId, e.getMessage());
- } catch (Exception e) {
- log.error("updateTransactionStatus:: failed to update transaction status: {}", e::getMessage);
- log.debug("updateTransactionStatus:: ", e);
- }
+ .ifPresent(newStatus -> dcbService.updateTransactionStatuses(newStatus, ecsTlr));
}
private void propagateChangesFromPrimaryToSecondaryRequest(EcsTlrEntity ecsTlr,
diff --git a/src/main/java/org/folio/service/impl/RequestServiceImpl.java b/src/main/java/org/folio/service/impl/RequestServiceImpl.java
index 313d4b8e..242c7c61 100644
--- a/src/main/java/org/folio/service/impl/RequestServiceImpl.java
+++ b/src/main/java/org/folio/service/impl/RequestServiceImpl.java
@@ -234,7 +234,7 @@ public CirculationItem createCirculationItem(Request request, String inventoryTe
.effectiveLocationId(item.getEffectiveLocationId())
.lendingLibraryCode("TEST_CODE");
- log.info("createCirculationItem:: creating circulation item {}", circulationItem.toString());
+ log.info("createCirculationItem:: creating circulation item {}", itemId);
return circulationItemClient.createCirculationItem(itemId, circulationItem);
}
diff --git a/src/main/resources/swagger.api/ecs-tlr.yaml b/src/main/resources/swagger.api/ecs-tlr.yaml
index dd60c16e..52bc6adb 100644
--- a/src/main/resources/swagger.api/ecs-tlr.yaml
+++ b/src/main/resources/swagger.api/ecs-tlr.yaml
@@ -90,9 +90,11 @@ components:
transactionStatusResponse:
$ref: 'schemas/transactionStatusResponse.yaml#/TransactionStatusResponse'
tenant:
- $ref: 'schemas/tenant.yaml#/Tenant'
+ $ref: 'schemas/consortia/tenant.yaml#/Tenant'
tenants:
- $ref: 'schemas/tenant.yaml#/TenantCollection'
+ $ref: 'schemas/consortia/tenant.yaml#/TenantCollection'
+ consortiaConfiguration:
+ $ref: 'schemas/consortia/consortiaConfiguration.yaml#/ConsortiaConfiguration'
publicationRequest:
$ref: 'schemas/publication.yaml#/PublicationRequest'
publicationResponse:
@@ -103,6 +105,8 @@ components:
$ref: 'schemas/request.json'
requests:
$ref: 'schemas/requests.json'
+ loan:
+ $ref: 'schemas/loan.json'
searchInstancesResponse:
$ref: 'schemas/search/searchInstancesResponse.yaml'
searchItemResponse:
diff --git a/src/main/resources/swagger.api/schemas/consortia/consortiaConfiguration.yaml b/src/main/resources/swagger.api/schemas/consortia/consortiaConfiguration.yaml
new file mode 100644
index 00000000..10b01b6d
--- /dev/null
+++ b/src/main/resources/swagger.api/schemas/consortia/consortiaConfiguration.yaml
@@ -0,0 +1,9 @@
+ConsortiaConfiguration:
+ type: "object"
+ description: "Consortia Configuration"
+ properties:
+ id:
+ type: "string"
+ format: "uuid"
+ centralTenantId:
+ type: "string"
\ No newline at end of file
diff --git a/src/main/resources/swagger.api/schemas/tenant.yaml b/src/main/resources/swagger.api/schemas/consortia/tenant.yaml
similarity index 100%
rename from src/main/resources/swagger.api/schemas/tenant.yaml
rename to src/main/resources/swagger.api/schemas/consortia/tenant.yaml
diff --git a/src/main/resources/swagger.api/schemas/loan.json b/src/main/resources/swagger.api/schemas/loan.json
new file mode 100644
index 00000000..1ab9653f
--- /dev/null
+++ b/src/main/resources/swagger.api/schemas/loan.json
@@ -0,0 +1,166 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "title": "Loan",
+ "description": "Links the item with the patron and applies certain conditions based on policies",
+ "properties": {
+ "id": {
+ "description": "Unique ID (generated UUID) of the loan",
+ "type": "string"
+ },
+ "userId": {
+ "description": "ID of the patron the item was lent to. Required for open loans, not required for closed loans (for anonymization).",
+ "type": "string"
+ },
+ "proxyUserId": {
+ "description": "ID of the user representing a proxy for the patron",
+ "type": "string",
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
+ },
+ "itemId": {
+ "description": "ID of the item lent to the patron",
+ "type": "string"
+ },
+ "itemEffectiveLocationIdAtCheckOut": {
+ "description": "The effective location, at the time of checkout, of the item loaned to the patron.",
+ "type": "string",
+ "$ref": "uuid.json"
+ },
+ "status": {
+ "description": "Overall status of the loan",
+ "type": "object",
+ "properties": {
+ "name": {
+ "description": "Name of the status (currently can be any value, values commonly used are Open and Closed)",
+ "type": "string"
+ }
+ }
+ },
+ "loanDate": {
+ "description": "Date time when the loan began (typically represented according to rfc3339 section-5.6. Has not had the date-time format validation applied as was not supported at point of introduction and would now be a breaking change)",
+ "type": "string"
+ },
+ "dueDate": {
+ "description": "Date time when the item is due to be returned",
+ "type": "string",
+ "format": "date-time"
+ },
+ "returnDate": {
+ "description": "Date time when the item is returned and the loan ends (typically represented according to rfc3339 section-5.6. Has not had the date-time format validation applied as was not supported at point of introduction and would now be a breaking change)",
+ "type": "string"
+ },
+ "systemReturnDate" : {
+ "description": "Date time when the returned item is actually processed",
+ "type": "string",
+ "format": "date-time"
+ },
+ "action": {
+ "description": "Last action performed on a loan (currently can be any value, values commonly used are checkedout and checkedin)",
+ "type": "string"
+ },
+ "actionComment": {
+ "description": "Comment to last action performed on a loan",
+ "type": "string"
+ },
+ "itemStatus": {
+ "description": "Last item status used in relation to this loan (currently can be any value, values commonly used are Checked out and Available)",
+ "type": "string"
+ },
+ "renewalCount": {
+ "description": "Count of how many times a loan has been renewed (incremented by the client)",
+ "type": "integer"
+ },
+ "loanPolicyId": {
+ "description": "ID of last policy used in relation to this loan",
+ "type": "string"
+ },
+ "checkoutServicePointId": {
+ "description": "ID of the Service Point where the last checkout occured",
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$",
+ "type": "string"
+ },
+ "checkinServicePointId": {
+ "description": "ID of the Service Point where the last checkin occured",
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$",
+ "type": "string"
+ },
+ "patronGroupIdAtCheckout": {
+ "description": "Patron Group Id at checkout",
+ "type": "string"
+ },
+ "dueDateChangedByRecall": {
+ "description": "Indicates whether or not this loan had its due date modified by a recall on the loaned item",
+ "type": "boolean"
+ },
+ "isDcb": {
+ "description": "Indicates whether or not this loan is associated for DCB use case",
+ "type": "boolean"
+ },
+ "declaredLostDate" : {
+ "description": "Date and time the item was declared lost during this loan",
+ "type": "string",
+ "format": "date-time"
+ },
+ "claimedReturnedDate": {
+ "description": "Date and time the item was claimed returned for this loan",
+ "type": "string",
+ "format": "date-time"
+ },
+ "overdueFinePolicyId": {
+ "description": "ID of overdue fines policy at the time the item is check-in or renewed",
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$",
+ "type": "string"
+ },
+ "lostItemPolicyId": {
+ "description": "ID of lost item policy which determines when the item ages to lost and the associated fees or the associated fees if the patron declares the item lost.",
+ "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$",
+ "type": "string"
+ },
+ "metadata": {
+ "description": "Metadata about creation and changes to loan, provided by the server (client should not provide)",
+ "type": "object",
+ "$ref": "metadata.json"
+ },
+ "agedToLostDelayedBilling": {
+ "description": "Aged to Lost Delayed Billing processing",
+ "type": "object",
+ "properties": {
+ "lostItemHasBeenBilled": {
+ "description": "Indicates if the aged to lost fee has been billed (for use where delayed billing is set up)",
+ "type": "boolean"
+ },
+ "dateLostItemShouldBeBilled": {
+ "description": "Indicates when the aged to lost fee should be billed (for use where delayed billing is set up)",
+ "type": "string",
+ "format": "date-time"
+ },
+ "agedToLostDate": {
+ "description": "Date and time the item was aged to lost for this loan",
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "reminders" : {
+ "description": "Information about reminders for overdue loan",
+ "type": "object",
+ "properties": {
+ "lastFeeBilled": {
+ "description": "Information about the most recent reminder fee billing",
+ "type": "object",
+ "properties": {
+ "number": {
+ "description": "Last reminder fee billed, sequence number",
+ "type": "integer"
+ },
+ "date": {
+ "description": "Last reminder fee billed, date",
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/folio/api/BaseIT.java b/src/test/java/org/folio/api/BaseIT.java
index 4e905b89..f836fe5d 100644
--- a/src/test/java/org/folio/api/BaseIT.java
+++ b/src/test/java/org/folio/api/BaseIT.java
@@ -1,5 +1,6 @@
package org.folio.api;
+import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -16,6 +17,8 @@
import org.apache.kafka.clients.admin.KafkaAdminClient;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.header.Header;
+import org.apache.kafka.common.header.internals.RecordHeader;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.FolioModuleMetadata;
import org.folio.spring.integration.XOkapiHeaders;
@@ -35,7 +38,6 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.kafka.core.KafkaTemplate;
-import org.springframework.messaging.MessageHeaders;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.DynamicPropertyRegistry;
@@ -74,6 +76,7 @@
public class BaseIT {
private static final String FOLIO_ENVIRONMENT = "folio";
protected static final String HEADER_TENANT = "x-okapi-tenant";
+ protected static final String USED_ID = "08d51c7a-0f36-4f3d-9e35-d285612a23df";
protected static final String TOKEN = "test_token";
protected static final String TENANT_ID_CONSORTIUM = "consortium"; // central tenant
protected static final String TENANT_ID_UNIVERSITY = "university";
@@ -156,22 +159,48 @@ public static String getOkapiUrl() {
protected static void setUpTenant(MockMvc mockMvc) {
mockMvc.perform(MockMvcRequestBuilders.post("/_/tenant")
.content(asJsonString(new TenantAttributes().moduleTo("mod-tlr")))
- .headers(defaultHeaders())
+ .headers(defaultHeadersForRequest())
.contentType(APPLICATION_JSON)).andExpect(status().isNoContent());
}
- public static HttpHeaders defaultHeaders() {
+ public static HttpHeaders defaultHeadersForRequest() {
final HttpHeaders httpHeaders = new HttpHeaders();
-
httpHeaders.setContentType(APPLICATION_JSON);
- httpHeaders.add(XOkapiHeaders.TENANT, TENANT_ID_CONSORTIUM);
- httpHeaders.add(XOkapiHeaders.URL, wireMockServer.baseUrl());
- httpHeaders.add(XOkapiHeaders.TOKEN, TOKEN);
- httpHeaders.add(XOkapiHeaders.USER_ID, "08d51c7a-0f36-4f3d-9e35-d285612a23df");
-
+ buildHeaders().forEach(httpHeaders::add);
return httpHeaders;
}
+ protected static Collection buildHeadersForKafkaProducer(String tenant) {
+ return buildKafkaHeaders(tenant)
+ .entrySet()
+ .stream()
+ .map(entry -> new RecordHeader(entry.getKey(), (byte[]) entry.getValue()))
+ .collect(toList());
+ }
+
+ protected static Map buildKafkaHeaders(String tenantId) {
+ Map headers = buildHeaders(tenantId);
+ headers.put("folio.tenantId", tenantId);
+
+ return headers.entrySet()
+ .stream()
+ .collect(toMap(Map.Entry::getKey, entry -> entry.getValue().getBytes()));
+ }
+
+ protected static Map buildHeaders() {
+ return buildHeaders(TENANT_ID_CONSORTIUM);
+ }
+
+ protected static Map buildHeaders(String tenantId) {
+ Map headers = new HashMap<>();
+ headers.put(XOkapiHeaders.TENANT, tenantId);
+ headers.put(XOkapiHeaders.URL, wireMockServer.baseUrl());
+ headers.put(XOkapiHeaders.TOKEN, TOKEN);
+ headers.put(XOkapiHeaders.USER_ID, USED_ID);
+ headers.put(XOkapiHeaders.REQUEST_ID, randomId());
+ return headers;
+ }
+
@SneakyThrows
public static String asJsonString(Object value) {
return OBJECT_MAPPER.writeValueAsString(value);
@@ -228,7 +257,7 @@ protected static String randomId() {
}
private static Map> buildDefaultHeaders() {
- return new HashMap<>(defaultHeaders().entrySet()
+ return new HashMap<>(defaultHeadersForRequest().entrySet()
.stream()
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)));
}
@@ -260,11 +289,4 @@ private static String buildTopicName(String env, String tenant, String module, S
return String.format("%s.%s.%s.%s", env, tenant, module, objectType);
}
- protected MessageHeaders getMessageHeaders(String tenantName, String tenantId) {
- Map header = new HashMap<>();
- header.put(XOkapiHeaders.TENANT, tenantName.getBytes());
- header.put("folio.tenantId", tenantId);
-
- return new MessageHeaders(header);
- }
}
diff --git a/src/test/java/org/folio/controller/KafkaEventListenerTest.java b/src/test/java/org/folio/controller/KafkaEventListenerTest.java
index 8e896c79..3462e274 100644
--- a/src/test/java/org/folio/controller/KafkaEventListenerTest.java
+++ b/src/test/java/org/folio/controller/KafkaEventListenerTest.java
@@ -27,8 +27,8 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import java.time.ZonedDateTime;
+import java.util.Collection;
import java.util.Date;
-import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@@ -37,7 +37,7 @@
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.TopicPartition;
-import org.apache.kafka.common.header.internals.RecordHeader;
+import org.apache.kafka.common.header.Header;
import org.awaitility.Awaitility;
import org.folio.api.BaseIT;
import org.folio.domain.dto.DcbItem;
@@ -622,11 +622,8 @@ private void publishEvent(String tenant, String topic, KafkaEvent event)
@SneakyThrows
private void publishEvent(String tenant, String topic, String payload) {
- kafkaTemplate.send(new ProducerRecord<>(topic, 0, randomId(), payload,
- List.of(
- new RecordHeader(XOkapiHeaders.TENANT, tenant.getBytes()),
- new RecordHeader("folio.tenantId", randomId().getBytes())
- )))
+ Collection headers = buildHeadersForKafkaProducer(tenant);
+ kafkaTemplate.send(new ProducerRecord<>(topic, 0, randomId(), payload, headers))
.get(10, SECONDS);
}
diff --git a/src/test/java/org/folio/listener/KafkaEventListenerTest.java b/src/test/java/org/folio/listener/KafkaEventListenerTest.java
index 759f8272..f1e698e5 100644
--- a/src/test/java/org/folio/listener/KafkaEventListenerTest.java
+++ b/src/test/java/org/folio/listener/KafkaEventListenerTest.java
@@ -8,6 +8,8 @@
import java.util.Map;
import org.folio.listener.kafka.KafkaEventListener;
+import org.folio.service.ConsortiaService;
+import org.folio.service.impl.LoanEventHandler;
import org.folio.service.impl.RequestBatchUpdateEventHandler;
import org.folio.service.impl.RequestEventHandler;
import org.folio.service.impl.UserEventHandler;
@@ -15,6 +17,7 @@
import org.folio.spring.service.SystemUserScopedExecutionService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.messaging.MessageHeaders;
@@ -24,6 +27,8 @@ class KafkaEventListenerTest {
@Mock
RequestEventHandler requestEventHandler;
@Mock
+ LoanEventHandler loanEventHandler;
+ @Mock
RequestBatchUpdateEventHandler requestBatchEventHandler;
@Mock
SystemUserScopedExecutionService systemUserScopedExecutionService;
@@ -31,14 +36,15 @@ class KafkaEventListenerTest {
UserGroupEventHandler userGroupEventHandler;
@Mock
UserEventHandler userEventHandler;
+ @Mock
+ ConsortiaService consortiaService;
+ @InjectMocks
+ KafkaEventListener kafkaEventListener;
@Test
void shouldHandleExceptionInEventHandler() {
doThrow(new NullPointerException("NPE")).when(systemUserScopedExecutionService)
.executeAsyncSystemUserScoped(any(), any());
- KafkaEventListener kafkaEventListener = new KafkaEventListener(requestEventHandler,
- requestBatchEventHandler, systemUserScopedExecutionService, userGroupEventHandler,
- userEventHandler);
kafkaEventListener.handleRequestEvent("{}",
new MessageHeaders(Map.of(TENANT, "default".getBytes())));
diff --git a/src/test/java/org/folio/service/DcbServiceTest.java b/src/test/java/org/folio/service/DcbServiceTest.java
new file mode 100644
index 00000000..3b7eb402
--- /dev/null
+++ b/src/test/java/org/folio/service/DcbServiceTest.java
@@ -0,0 +1,103 @@
+package org.folio.service;
+
+import static java.util.UUID.randomUUID;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import org.folio.client.feign.DcbTransactionClient;
+import org.folio.domain.dto.TransactionStatus;
+import org.folio.domain.dto.TransactionStatusResponse;
+import org.folio.service.impl.DcbServiceImpl;
+import org.folio.spring.service.SystemUserScopedExecutionService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class DcbServiceTest {
+
+ @Mock
+ private DcbTransactionClient dcbTransactionClient;
+ @Mock
+ private SystemUserScopedExecutionService executionService;
+ @InjectMocks
+ private DcbServiceImpl dcbService;
+
+ @BeforeEach
+ public void setup() {
+ // Bypass the use of system user and return the result of Callable immediately
+ when(executionService.executeSystemUserScoped(any(String.class), any(Callable.class)))
+ .thenAnswer(invocation -> invocation.getArgument(1, Callable.class).call());
+ }
+
+ @ParameterizedTest
+ @CsvSource(value = {
+ "PICKUP, CREATED, OPEN, true",
+ "PICKUP, OPEN, AWAITING_PICKUP, false",
+ "PICKUP, AWAITING_PICKUP, ITEM_CHECKED_OUT, false",
+ "PICKUP, ITEM_CHECKED_OUT, ITEM_CHECKED_IN, false",
+ "PICKUP, ITEM_CHECKED_IN, CLOSED, true",
+ "PICKUP, OPEN, CANCELLED, true",
+
+ "BORROWING-PICKUP, CREATED, OPEN, true",
+ "BORROWING-PICKUP, OPEN, AWAITING_PICKUP, false",
+ "BORROWING-PICKUP, AWAITING_PICKUP, ITEM_CHECKED_OUT, false",
+ "BORROWING-PICKUP, ITEM_CHECKED_OUT, ITEM_CHECKED_IN, false",
+ "BORROWING-PICKUP, ITEM_CHECKED_IN, CLOSED, true",
+ "BORROWING-PICKUP, OPEN, CANCELLED, true",
+
+ "BORROWER, CREATED, OPEN, true",
+ "BORROWER, OPEN, AWAITING_PICKUP, true",
+ "BORROWER, AWAITING_PICKUP, ITEM_CHECKED_OUT, true",
+ "BORROWER, ITEM_CHECKED_OUT, ITEM_CHECKED_IN, true",
+ "BORROWER, ITEM_CHECKED_IN, CLOSED, true",
+ "BORROWER, OPEN, CANCELLED, true",
+
+ "LENDER, CREATED, OPEN, true",
+ "LENDER, OPEN, AWAITING_PICKUP, true",
+ "LENDER, AWAITING_PICKUP, ITEM_CHECKED_OUT, true",
+ "LENDER, ITEM_CHECKED_OUT, ITEM_CHECKED_IN, true",
+ "LENDER, ITEM_CHECKED_IN, CLOSED, false",
+ "LENDER, OPEN, CANCELLED, true",
+ })
+ void updateTransactionStatusesUpdatesAllTransactions(String role, String oldStatus,
+ String newStatus, boolean transactionUpdateIsExpected) {
+
+ String transactionId = randomUUID().toString();
+ TransactionStatus newTransactionStatus = new TransactionStatus().status(
+ TransactionStatus.StatusEnum.fromValue(newStatus));
+
+ TransactionStatusResponse mockGetStatusResponse = buildTransactionStatusResponse(role, oldStatus);
+ TransactionStatusResponse mockUpdateStatusResponse = buildTransactionStatusResponse(role, newStatus);
+
+ when(dcbTransactionClient.getDcbTransactionStatus(transactionId))
+ .thenReturn(mockGetStatusResponse);
+
+ if (transactionUpdateIsExpected) {
+ when(dcbTransactionClient.changeDcbTransactionStatus(transactionId, newTransactionStatus))
+ .thenReturn(mockUpdateStatusResponse);
+ }
+
+ dcbService.updateTransactionStatus(UUID.fromString(transactionId),
+ newTransactionStatus.getStatus(), "test_tenant");
+
+ verify(dcbTransactionClient, times(transactionUpdateIsExpected ? 1 : 0))
+ .changeDcbTransactionStatus(transactionId, newTransactionStatus);
+ }
+
+ private static TransactionStatusResponse buildTransactionStatusResponse(String role, String status) {
+ return new TransactionStatusResponse()
+ .role(TransactionStatusResponse.RoleEnum.fromValue(role))
+ .status(TransactionStatusResponse.StatusEnum.fromValue(status));
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/folio/service/LoanEventHandlerTest.java b/src/test/java/org/folio/service/LoanEventHandlerTest.java
new file mode 100644
index 00000000..294e4adc
--- /dev/null
+++ b/src/test/java/org/folio/service/LoanEventHandlerTest.java
@@ -0,0 +1,199 @@
+package org.folio.service;
+
+import static java.util.Collections.emptyList;
+import static java.util.UUID.randomUUID;
+import static org.folio.support.KafkaEvent.EventType.UPDATED;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.UUID;
+
+import org.folio.domain.dto.Loan;
+import org.folio.domain.dto.TransactionStatus;
+import org.folio.domain.dto.TransactionStatusResponse;
+import org.folio.domain.entity.EcsTlrEntity;
+import org.folio.repository.EcsTlrRepository;
+import org.folio.service.impl.LoanEventHandler;
+import org.folio.support.KafkaEvent;
+import org.folio.support.KafkaEvent.EventType;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class LoanEventHandlerTest {
+
+ private static final EnumSet SUPPORTED_EVENT_TYPES = EnumSet.of(UPDATED);
+
+ @Mock
+ private DcbService dcbService;
+ @Mock
+ private EcsTlrRepository ecsTlrRepository;
+ @InjectMocks
+ private LoanEventHandler loanEventHandler;
+
+ @ParameterizedTest
+ @EnumSource(EventType.class)
+ void eventsOfUnsupportedTypesAreIgnored(EventType eventType) {
+ if (!SUPPORTED_EVENT_TYPES.contains(eventType)) {
+ loanEventHandler.handle(new KafkaEvent<>(null, null, eventType, 0L, null, null));
+ verifyNoInteractions(ecsTlrRepository, dcbService);
+ }
+ }
+
+ @Test
+ void updateEventForLoanWithUnsupportedActionInIgnored() {
+ Loan loan = new Loan().action("random_action");
+ KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED,
+ 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant");
+ loanEventHandler.handle(event);
+ verifyNoInteractions(ecsTlrRepository, dcbService);
+ }
+
+ @Test
+ void checkInEventIsIgnoredWhenEcsTlrForUpdatedLoanIsNotFound() {
+ UUID itemId = randomUUID();
+ UUID userId = randomUUID();
+ Loan loan = new Loan()
+ .id(randomUUID().toString())
+ .action("checkedin")
+ .itemId(itemId.toString())
+ .userId(userId.toString());
+
+ when(ecsTlrRepository.findByItemId(itemId))
+ .thenReturn(emptyList());
+
+ KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED,
+ 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant");
+ loanEventHandler.handle(event);
+
+ verify(ecsTlrRepository).findByItemId(itemId);
+ verifyNoInteractions(dcbService);
+ }
+
+ @Test
+ void checkInEventIsIgnoredWhenEcsTlrDoesNotContainsNoTransactionIds() {
+ UUID itemId = randomUUID();
+ UUID userId = randomUUID();
+ Loan loan = new Loan()
+ .id(randomUUID().toString())
+ .action("checkedin")
+ .itemId(itemId.toString())
+ .userId(userId.toString());
+
+ when(ecsTlrRepository.findByItemId(itemId))
+ .thenReturn(List.of(new EcsTlrEntity()));
+
+ KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED,
+ 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant");
+ loanEventHandler.handle(event);
+
+ verify(ecsTlrRepository).findByItemId(itemId);
+ verifyNoInteractions(dcbService);
+ }
+
+ @Test
+ void checkInEventIsIgnoredWhenEventTenantDoesNotMatchEcsRequestTransactionTenants() {
+ UUID itemId = randomUUID();
+ UUID userId = randomUUID();
+ Loan loan = new Loan()
+ .id(randomUUID().toString())
+ .action("checkedin")
+ .itemId(itemId.toString())
+ .userId(userId.toString());
+
+ EcsTlrEntity ecsTlr = new EcsTlrEntity();
+ ecsTlr.setPrimaryRequestTenantId("borrowing_tenant");
+ ecsTlr.setSecondaryRequestTenantId("lending_tenant");
+ ecsTlr.setPrimaryRequestDcbTransactionId(randomUUID());
+ ecsTlr.setSecondaryRequestDcbTransactionId(randomUUID());
+
+ when(ecsTlrRepository.findByItemId(itemId))
+ .thenReturn(List.of(ecsTlr));
+
+ KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), "test_tenant", UPDATED,
+ 0L, new KafkaEvent.EventData<>(loan, loan), "test_tenant");
+ loanEventHandler.handle(event);
+
+ verify(ecsTlrRepository).findByItemId(itemId);
+ verifyNoInteractions(dcbService);
+ }
+
+ @ParameterizedTest
+ @CsvSource(value = {
+ "BORROWING-PICKUP, ITEM_CHECKED_OUT, LENDER, ITEM_CHECKED_OUT, borrowing_tenant, ITEM_CHECKED_IN",
+ "BORROWING-PICKUP, ITEM_CHECKED_IN, LENDER, ITEM_CHECKED_OUT, borrowing_tenant, ITEM_CHECKED_IN",
+ "PICKUP, ITEM_CHECKED_OUT, LENDER, ITEM_CHECKED_OUT, borrowing_tenant, ITEM_CHECKED_IN",
+ "PICKUP, ITEM_CHECKED_IN, LENDER, ITEM_CHECKED_OUT, borrowing_tenant, ITEM_CHECKED_IN",
+
+ "BORROWING-PICKUP, ITEM_CHECKED_IN, LENDER, ITEM_CHECKED_IN, lending_tenant, CLOSED",
+ "BORROWING-PICKUP, ITEM_CHECKED_IN, LENDER, CLOSED, lending_tenant, CLOSED",
+ "PICKUP, ITEM_CHECKED_IN, LENDER, ITEM_CHECKED_IN, lending_tenant, CLOSED",
+ "PICKUP, ITEM_CHECKED_IN, LENDER, CLOSED, lending_tenant, CLOSED"
+ })
+ void checkInEventIsHandled(String primaryTransactionRole, String primaryTransactionStatus,
+ String secondaryTransactionRole, String secondaryTransactionStatus, String eventTenant,
+ String expectedNewTransactionStatus) {
+
+ String primaryRequestTenant = "borrowing_tenant";
+ String secondaryRequestTenant = "lending_tenant";
+ UUID primaryTransactionId = randomUUID();
+ UUID secondaryTransactionId = randomUUID();
+ UUID itemId = randomUUID();
+ UUID userId = randomUUID();
+ Loan loan = new Loan()
+ .action("checkedin")
+ .itemId(itemId.toString())
+ .userId(userId.toString());
+
+ EcsTlrEntity mockEcsTlr = new EcsTlrEntity();
+ mockEcsTlr.setId(randomUUID());
+ mockEcsTlr.setPrimaryRequestTenantId(primaryRequestTenant);
+ mockEcsTlr.setSecondaryRequestTenantId(secondaryRequestTenant);
+ mockEcsTlr.setPrimaryRequestDcbTransactionId(primaryTransactionId);
+ mockEcsTlr.setSecondaryRequestDcbTransactionId(secondaryTransactionId);
+
+ when(ecsTlrRepository.findByItemId(itemId))
+ .thenReturn(List.of(mockEcsTlr));
+
+ TransactionStatusResponse mockPrimaryTransactionResponse = buildTransactionStatusResponse(
+ primaryTransactionRole, primaryTransactionStatus);
+ TransactionStatusResponse mockSecondaryTransactionResponse = buildTransactionStatusResponse(
+ secondaryTransactionRole, secondaryTransactionStatus);
+
+ when(dcbService.getTransactionStatus(primaryTransactionId, primaryRequestTenant))
+ .thenReturn(mockPrimaryTransactionResponse);
+ when(dcbService.getTransactionStatus(secondaryTransactionId, secondaryRequestTenant))
+ .thenReturn(mockSecondaryTransactionResponse);
+
+ TransactionStatus.StatusEnum expectedNewStatus = TransactionStatus.StatusEnum.fromValue(
+ expectedNewTransactionStatus);
+ doNothing().when(dcbService).updateTransactionStatuses(expectedNewStatus, mockEcsTlr);
+
+ KafkaEvent.EventData eventData = new KafkaEvent.EventData<>(loan, loan);
+ KafkaEvent event = new KafkaEvent<>(randomUUID().toString(), eventTenant,
+ UPDATED, 0L, eventData, eventTenant);
+
+ loanEventHandler.handle(event);
+
+ verify(ecsTlrRepository).findByItemId(itemId);
+ verify(dcbService).getTransactionStatus(primaryTransactionId, primaryRequestTenant);
+ verify(dcbService).getTransactionStatus(secondaryTransactionId, secondaryRequestTenant);
+ verify(dcbService).updateTransactionStatuses(expectedNewStatus, mockEcsTlr);
+ }
+
+ private static TransactionStatusResponse buildTransactionStatusResponse(String role, String status) {
+ return new TransactionStatusResponse()
+ .role(TransactionStatusResponse.RoleEnum.fromValue(role))
+ .status(TransactionStatusResponse.StatusEnum.fromValue(status));
+ }
+}
diff --git a/src/test/java/org/folio/service/RequestBatchUpdateEventHandlerTest.java b/src/test/java/org/folio/service/RequestBatchUpdateEventHandlerTest.java
index 8425f55c..2de1dcd0 100644
--- a/src/test/java/org/folio/service/RequestBatchUpdateEventHandlerTest.java
+++ b/src/test/java/org/folio/service/RequestBatchUpdateEventHandlerTest.java
@@ -116,7 +116,7 @@ void shouldReorderTwoSecondaryRequestsWhenPrimaryRequestsReordered() {
null, new RequestsBatchUpdate()
.instanceId(instanceId)
.requestLevel(RequestsBatchUpdate.RequestLevelEnum.TITLE))),
- getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID));
+ buildKafkaHeaders(CENTRAL_TENANT_ID));
verify(requestService, times(1)).reorderRequestsQueueForInstance(
instanceId, firstTenant, reorderQueue);
@@ -193,7 +193,7 @@ void shouldReorderThreeSecondaryRequestsWhenPrimaryRequestsReordered() {
null, new RequestsBatchUpdate()
.instanceId(instanceId)
.requestLevel(RequestsBatchUpdate.RequestLevelEnum.TITLE))),
- getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID));
+ buildKafkaHeaders(CENTRAL_TENANT_ID));
verify(requestService, times(1)).reorderRequestsQueueForInstance(
instanceId, firstTenant, reorderQueue);
@@ -250,7 +250,7 @@ void shouldNotReorderSecondaryRequestsWhenPrimaryRequestsOrderIsUnchanged() {
null, new RequestsBatchUpdate()
.instanceId(instanceId)
.requestLevel(RequestsBatchUpdate.RequestLevelEnum.TITLE))),
- getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID));
+ buildKafkaHeaders(CENTRAL_TENANT_ID));
verify(requestService, times(0)).reorderRequestsQueueForInstance(
eq(instanceId), eq(firstTenant), any());
@@ -308,7 +308,7 @@ void shouldNotReorderSecondaryRequestsWhenPrimaryRequestsAreNullOrEmtpy(
null, new RequestsBatchUpdate()
.instanceId(instanceId)
.requestLevel(RequestsBatchUpdate.RequestLevelEnum.TITLE))),
- getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID));
+ buildKafkaHeaders(CENTRAL_TENANT_ID));
verify(requestService, times(0)).reorderRequestsQueueForInstance(
eq(instanceId), eq(firstTenant), any());
@@ -377,7 +377,7 @@ null, new RequestsBatchUpdate()
.instanceId(instanceId)
.itemId(itemId)
.requestLevel(RequestsBatchUpdate.RequestLevelEnum.ITEM))),
- getMessageHeaders(CENTRAL_TENANT_ID, CENTRAL_TENANT_ID));
+ buildKafkaHeaders(CENTRAL_TENANT_ID));
verify(requestService, times(1)).reorderRequestsQueueForItem(
itemId, firstTenant, reorderQueue);
diff --git a/src/test/java/org/folio/service/RequestEventHandlerTest.java b/src/test/java/org/folio/service/RequestEventHandlerTest.java
index 70c2615b..a1172aee 100644
--- a/src/test/java/org/folio/service/RequestEventHandlerTest.java
+++ b/src/test/java/org/folio/service/RequestEventHandlerTest.java
@@ -8,7 +8,6 @@
import static org.mockito.Mockito.when;
import java.util.Optional;
-import java.util.UUID;
import org.folio.api.BaseIT;
import org.folio.listener.kafka.KafkaEventListener;
@@ -35,8 +34,7 @@ class RequestEventHandlerTest extends BaseIT {
void handleRequestUpdateTest() {
when(ecsTlrRepository.findBySecondaryRequestId(any())).thenReturn(Optional.of(getEcsTlrEntity()));
doNothing().when(dcbService).createLendingTransaction(any());
- eventListener.handleRequestEvent(REQUEST_UPDATE_EVENT_SAMPLE, getMessageHeaders(
- TENANT_ID_CONSORTIUM, UUID.randomUUID().toString()));
+ eventListener.handleRequestEvent(REQUEST_UPDATE_EVENT_SAMPLE, buildKafkaHeaders(TENANT_ID_CONSORTIUM));
verify(ecsTlrRepository).findBySecondaryRequestId(any());
}
}
diff --git a/src/test/java/org/folio/service/UserEventHandlerTest.java b/src/test/java/org/folio/service/UserEventHandlerTest.java
index 94569125..7e7f4035 100644
--- a/src/test/java/org/folio/service/UserEventHandlerTest.java
+++ b/src/test/java/org/folio/service/UserEventHandlerTest.java
@@ -23,6 +23,7 @@ void handleUserUpdatingEventShouldUpdateUserForAllDataTenants() {
when(userTenantsService.findFirstUserTenant()).thenReturn(mockUserTenant());
when(consortiaService.getAllConsortiumTenants(anyString())).thenReturn(mockTenantCollection());
when(userService.update(any(User.class))).thenReturn(new User());
+ when(consortiaService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID);
doAnswer(invocation -> {
((Runnable) invocation.getArguments()[1]).run();
@@ -30,8 +31,7 @@ void handleUserUpdatingEventShouldUpdateUserForAllDataTenants() {
}).when(systemUserScopedExecutionService).executeAsyncSystemUserScoped(anyString(),
any(Runnable.class));
- eventListener.handleUserEvent(USER_UPDATING_EVENT_SAMPLE,
- getMessageHeaders(TENANT, TENANT_ID));
+ eventListener.handleUserEvent(USER_UPDATING_EVENT_SAMPLE, buildKafkaHeaders(CENTRAL_TENANT_ID));
verify(systemUserScopedExecutionService, times(3))
.executeAsyncSystemUserScoped(anyString(), any(Runnable.class));
diff --git a/src/test/java/org/folio/service/UserGroupEventHandlerTest.java b/src/test/java/org/folio/service/UserGroupEventHandlerTest.java
index 6b92c7f5..5b6c227f 100644
--- a/src/test/java/org/folio/service/UserGroupEventHandlerTest.java
+++ b/src/test/java/org/folio/service/UserGroupEventHandlerTest.java
@@ -30,6 +30,7 @@ void handleUserGroupCreatingEventShouldCreateUserGroupForAllDataTenants() {
when(userTenantsService.findFirstUserTenant()).thenReturn(mockUserTenant());
when(consortiaService.getAllConsortiumTenants(anyString())).thenReturn(mockTenantCollection());
when(userGroupService.create(any(UserGroup.class))).thenReturn(new UserGroup());
+ when(consortiaService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID);
doAnswer(invocation -> {
((Runnable) invocation.getArguments()[1]).run();
@@ -38,7 +39,7 @@ void handleUserGroupCreatingEventShouldCreateUserGroupForAllDataTenants() {
any(Runnable.class));
eventListener.handleUserGroupEvent(USER_GROUP_CREATING_EVENT_SAMPLE,
- getMessageHeaders(TENANT, TENANT_ID));
+ buildKafkaHeaders(CENTRAL_TENANT_ID));
verify(systemUserScopedExecutionService, times(3)).executeAsyncSystemUserScoped(anyString(),
any(Runnable.class));
@@ -50,6 +51,7 @@ void handleUserGroupUpdatingEventShouldUpdateUserGroupForAllDataTenants() {
when(userTenantsService.findFirstUserTenant()).thenReturn(mockUserTenant());
when(consortiaService.getAllConsortiumTenants(anyString())).thenReturn(mockTenantCollection());
when(userGroupService.update(any(UserGroup.class))).thenReturn(new UserGroup());
+ when(consortiaService.getCentralTenantId()).thenReturn(CENTRAL_TENANT_ID);
doAnswer(invocation -> {
((Runnable) invocation.getArguments()[1]).run();
@@ -58,7 +60,7 @@ void handleUserGroupUpdatingEventShouldUpdateUserGroupForAllDataTenants() {
any(Runnable.class));
eventListener.handleUserGroupEvent(USER_GROUP_UPDATING_EVENT_SAMPLE,
- getMessageHeaders(TENANT, TENANT_ID));
+ buildKafkaHeaders(CENTRAL_TENANT_ID));
verify(systemUserScopedExecutionService, times(3))
.executeAsyncSystemUserScoped(anyString(), any(Runnable.class));
diff --git a/src/test/resources/mappings/consortiaConfiguration.json b/src/test/resources/mappings/consortiaConfiguration.json
new file mode 100644
index 00000000..b978e6df
--- /dev/null
+++ b/src/test/resources/mappings/consortiaConfiguration.json
@@ -0,0 +1,20 @@
+{
+ "mappings": [
+ {
+ "request": {
+ "method": "GET",
+ "url": "/consortia-configuration"
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/json"
+ },
+ "jsonBody": {
+ "id": "0bc8835b-1233-48ba-bc75-979cb04dc06e",
+ "centralTenantId": "consortium"
+ }
+ }
+ }
+ ]
+}