Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Switch DCB user check from lastName to the user type ([CIRC-2482](https://folio-org.atlassian.net/browse/CIRC-2482))
* Upgrade wiremock from 2.35.0 to 3.13.2 fixing CVE-2023-41329 ([CIRC-2543](https://folio-org.atlassian.net/browse/CIRC-2543))
* Fix request losing retrieval service point name upon check-in ([CIRC-2535](https://folio-org.atlassian.net/browse/CIRC-2535))
* Fix circulation log showing incorrect source for loan renewals ([CIRC-2550](https://folio-org.atlassian.net/browse/CIRC-2550))

## 24.4.0 2025-03-12
* Patron notices for the trigger “Item recalled” not sent if the item is not 1st in the title request queue (CIRC-2168)
Expand Down
21 changes: 13 additions & 8 deletions src/main/java/org/folio/circulation/services/EventPublisher.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public CompletableFuture<Result<LoanAndRelatedRecords>> publishDueDateChangedEve
loanAndRelatedRecords.getLoan() != null ? loanAndRelatedRecords.getLoan().getId() : "null");
if (loanAndRelatedRecords.getLoan() != null) {
Loan loan = loanAndRelatedRecords.getLoan();
publishDueDateChangedEvent(loan, loan.getUser(), false);
publishDueDateChangedEvent(loan, loan.getUser(), null);
}

return completedFuture(succeeded(loanAndRelatedRecords));
Expand All @@ -201,7 +201,7 @@ public CompletableFuture<Result<RenewalContext>> publishDueDateChangedEvent(
renewalContext.getLoan() != null ? renewalContext.getLoan().getId() : "null");
var loan = renewalContext.getLoan();

publishDueDateChangedEvent(loan, loan.getUser(), true);
publishDueDateChangedEvent(loan, loan.getUser(), renewalContext);

return completedFuture(succeeded(renewalContext));
}
Expand Down Expand Up @@ -317,13 +317,15 @@ public CompletableFuture<Result<Void>> publishDueDateLogEvent(Loan loan) {
.thenCompose(loanLogContext -> loanLogContext.after(ctx -> publishLogRecord(ctx, LOAN)));
}

public CompletableFuture<Result<Void>> publishRenewedEvent(Loan loan) {
logger.info("publishRenewedEvent:: parameters loanId: {}", loan::getId);
private CompletableFuture<Result<Void>> publishRenewedEvent(Loan loan, String updatedByUserId) {
logger.info("publishRenewedEvent:: parameters loanId: {}, updatedByUserId: {}",
loan::getId, () -> updatedByUserId);
return getTenantTimeZone()
.thenApply(zoneResult -> zoneResult.map(zoneId -> {
var logDescription = getLoanDueDateChangeLog(loan, zoneId);
return LoanLogContext.from(loan)
.withDescription(logDescription)
.withUpdatedByUserId(updatedByUserId)
.asJson();
}))
.thenCompose(loanLogContext -> loanLogContext.after(ctx -> publishLogRecord(ctx, LOAN)));
Expand Down Expand Up @@ -467,10 +469,12 @@ private CompletableFuture<Result<Loan>> publishDueDateChangedEvent(Loan loan, Re
if (records.getRecalledLoanPreviousDueDate() != null) {
loan.setPreviousDueDate(records.getRecalledLoanPreviousDueDate());
}
return publishDueDateChangedEvent(loan, records.getRequest().getRequester(), false);
return publishDueDateChangedEvent(loan, records.getRequest().getRequester(), null);
}

private CompletableFuture<Result<Loan>> publishDueDateChangedEvent(Loan loan, User user, boolean renewalContext) {
private CompletableFuture<Result<Loan>> publishDueDateChangedEvent(Loan loan, User user,
RenewalContext renewalContext) {

if (loan != null) {
JsonObject payloadJsonObject = new JsonObject();
write(payloadJsonObject, USER_ID_FIELD, loan.getUserId());
Expand All @@ -479,8 +483,9 @@ private CompletableFuture<Result<Loan>> publishDueDateChangedEvent(Loan loan, Us
write(payloadJsonObject, DUE_DATE_CHANGED_BY_RECALL_FIELD, loan.wasDueDateChangedByRecall());

runAsync(() -> publishDueDateLogEvent(loan));
if (renewalContext) {
runAsync(() -> publishRenewedEvent(loan.copy().withUser(user)));
if (renewalContext != null) {
runAsync(() -> publishRenewedEvent(loan.copy().withUser(user),
renewalContext.getLoggedInUserId()));
}

return pubSubPublishingService.publishEvent(LOAN_DUE_DATE_CHANGED.name(), payloadJsonObject.encode())
Expand Down
100 changes: 100 additions & 0 deletions src/test/java/org/folio/circulation/services/EventPublisherTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.folio.circulation.services;

import static java.util.concurrent.ForkJoinPool.commonPool;
import static org.folio.circulation.domain.EventType.LOG_RECORD;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.time.ZonedDateTime;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import org.folio.circulation.domain.Loan;
import org.folio.circulation.resources.context.RenewalContext;
import org.folio.circulation.support.Clients;
import org.folio.circulation.support.CollectionResourceClient;
import org.folio.circulation.support.GetManyRecordsClient;
import org.folio.circulation.support.ServerErrorFailure;
import org.folio.circulation.support.results.Result;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import io.vertx.core.json.JsonObject;

@ExtendWith(MockitoExtension.class)
class EventPublisherTest {

@Mock
private Clients clients;
@Mock
private PubSubPublishingService pubSubPublishingService;
@Mock
private CollectionResourceClient localeClient;
@Mock
private GetManyRecordsClient settingsStorageClient;

private EventPublisher eventPublisher;

@BeforeEach
void setUp() {
when(clients.pubSubPublishingService()).thenReturn(pubSubPublishingService);
when(clients.localeClient()).thenReturn(localeClient);
when(clients.settingsStorageClient()).thenReturn(settingsStorageClient);
when(pubSubPublishingService.publishEvent(anyString(), anyString()))
.thenReturn(CompletableFuture.completedFuture(true));
when(localeClient.get())
.thenReturn(CompletableFuture.completedFuture(
Result.failed(new ServerErrorFailure("locale not available"))));

eventPublisher = new EventPublisher(clients);
}

@Test
void renewalCirculationLogSourceShouldBeRenewalStaffNotCheckoutStaff() throws Exception {
String checkoutStaffId = UUID.randomUUID().toString();
String renewalStaffId = UUID.randomUUID().toString();
ZonedDateTime previousDueDate = ZonedDateTime.now().minusDays(7);
ZonedDateTime newDueDate = ZonedDateTime.now().plusDays(7);

Loan loan = Loan.from(new JsonObject()
.put("id", UUID.randomUUID().toString())
.put("userId", UUID.randomUUID().toString())
.put("itemId", UUID.randomUUID().toString())
.put("action", "renewed")
.put("dueDate", newDueDate.toString())
.put("status", new JsonObject().put("name", "Open"))
.put("metadata", new JsonObject()
.put("updatedByUserId", checkoutStaffId)));
loan.setPreviousDueDate(previousDueDate);

RenewalContext renewalContext = RenewalContext.create(loan, new JsonObject(), renewalStaffId);
eventPublisher.publishDueDateChangedEvent(renewalContext).get();
commonPool().awaitQuiescence(5, TimeUnit.SECONDS);

ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
verify(pubSubPublishingService, atLeastOnce())
.publishEvent(eq(LOG_RECORD.name()), payloadCaptor.capture());

JsonObject renewalLogPayload = payloadCaptor.getAllValues().stream()
.map(JsonObject::new)
.filter(json -> "Renewed".equals(
json.getJsonObject("payload", new JsonObject()).getString("action")))
.findFirst()
.orElseThrow(() -> new AssertionError("No renewal LOG_RECORD event was published"));
String updatedByUserId = renewalLogPayload.getJsonObject("payload").getString("updatedByUserId");

assertThat(updatedByUserId, is(renewalStaffId));
assertThat(updatedByUserId, is(not(checkoutStaffId)));
}
}