diff --git a/NEWS.md b/NEWS.md index e84b8ab8e3..a30880ba6f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +## 22.0.2 2021-08-03 + +* Increases the number of loans to be checked for scheduled anonymization to 50 000 (CIRC-1178) + ## 22.0.1 2021-08-03 * User asked to override patron block on renewing when no patron block exists on renewing (CIRC-1185) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index e1ac359500..04f9cad5d0 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -828,7 +828,7 @@ "pubsub.publish.post" ], "unit": "minute", - "delay": "1" + "delay": "60" }, { "methods": [ @@ -2257,21 +2257,23 @@ ], "visible": false } - - ], "launchDescriptor": { "dockerImage": "${artifactId}:${version}", "dockerPull": false, "dockerArgs": { "HostConfig": { - "Memory": 715827882, + "Memory": 1073741824, "PortBindings": { "9801/tcp": [ { "HostPort": "%p" } ] } } }, "env": [ { "name": "JAVA_OPTIONS", "value": "-XX:MaxRAMPercentage=66.0" + }, + { + "name": "SCHEDULED_ANONYMIZATION_NUMBER_OF_LOANS_TO_CHECK", + "value": "50000" } ] } diff --git a/src/main/java/org/folio/Environment.java b/src/main/java/org/folio/Environment.java new file mode 100644 index 0000000000..6953b03119 --- /dev/null +++ b/src/main/java/org/folio/Environment.java @@ -0,0 +1,36 @@ +package org.folio; + +import static java.lang.Integer.parseInt; +import static org.apache.commons.lang.StringUtils.isBlank; + +import java.lang.invoke.MethodHandles; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class Environment { + private static final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); + + private Environment() { } + + public static int getScheduledAnonymizationNumberOfLoansToCheck() { + return getVariable("SCHEDULED_ANONYMIZATION_NUMBER_OF_LOANS_TO_CHECK", 50000); + } + + private static int getVariable(String key, int defaultValue) { + final var variable = System.getenv().get(key); + + if (isBlank(variable)) { + return defaultValue; + } + + try { + return parseInt(variable); + } + catch(Exception e) { + log.warn("Invalid value for '{}': '{}' ", key, variable); + + return defaultValue; + } + } +} diff --git a/src/main/java/org/folio/circulation/domain/anonymization/DefaultLoanAnonymizationService.java b/src/main/java/org/folio/circulation/domain/anonymization/DefaultLoanAnonymizationService.java index 525ef7a4b1..c617b1b24e 100644 --- a/src/main/java/org/folio/circulation/domain/anonymization/DefaultLoanAnonymizationService.java +++ b/src/main/java/org/folio/circulation/domain/anonymization/DefaultLoanAnonymizationService.java @@ -3,35 +3,42 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import static org.folio.circulation.domain.anonymization.LoanAnonymizationRecords.CAN_BE_ANONYMIZED_KEY; +import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; -import org.folio.circulation.infrastructure.storage.loans.AnonymizeStorageLoansRepository; +import org.folio.circulation.domain.Loan; import org.folio.circulation.domain.anonymization.service.AnonymizationCheckersService; -import org.folio.circulation.domain.anonymization.service.LoanAnonymizationFinderService; +import org.folio.circulation.infrastructure.storage.loans.AnonymizeStorageLoansRepository; import org.folio.circulation.services.EventPublisher; import org.folio.circulation.support.results.Result; public class DefaultLoanAnonymizationService implements LoanAnonymizationService { private final AnonymizeStorageLoansRepository anonymizeStorageLoansRepository; private final AnonymizationCheckersService anonymizationCheckersService; - private final LoanAnonymizationFinderService loansFinder; private final EventPublisher eventPublisher; - DefaultLoanAnonymizationService(AnonymizationCheckersService anonymizationCheckersService, - LoanAnonymizationFinderService loansFinderService, - AnonymizeStorageLoansRepository anonymizeStorageLoansRepository, EventPublisher eventPublisher) { + public DefaultLoanAnonymizationService( + AnonymizationCheckersService anonymizationCheckersService, + AnonymizeStorageLoansRepository anonymizeStorageLoansRepository, + EventPublisher eventPublisher) { this.anonymizationCheckersService = anonymizationCheckersService; - this.loansFinder = loansFinderService; this.anonymizeStorageLoansRepository = anonymizeStorageLoansRepository; this.eventPublisher = eventPublisher; } @Override - public CompletableFuture> anonymizeLoans() { - return loansFinder.findLoansToAnonymize() + public CompletableFuture> anonymizeLoans( + Supplier>>> loansToCheck) { + + if (anonymizationCheckersService.neverAnonymizeLoans()) { + return completedFuture(Result.of(LoanAnonymizationRecords::new)); + } + + return loansToCheck.get() .thenApply(r -> r.map(new LoanAnonymizationRecords()::withLoansFound)) .thenCompose(this::segregateLoanRecords) .thenCompose(r -> r.after(anonymizeStorageLoansRepository::postAnonymizeStorageLoans)) diff --git a/src/main/java/org/folio/circulation/domain/anonymization/LoanAnonymization.java b/src/main/java/org/folio/circulation/domain/anonymization/LoanAnonymization.java deleted file mode 100644 index 85f7a4aaf8..0000000000 --- a/src/main/java/org/folio/circulation/domain/anonymization/LoanAnonymization.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.folio.circulation.domain.anonymization; - -import static org.folio.circulation.support.http.client.PageLimit.limit; - -import java.lang.invoke.MethodHandles; - -import org.folio.circulation.domain.anonymization.config.ClosingType; -import org.folio.circulation.domain.anonymization.config.LoanAnonymizationConfiguration; -import org.folio.circulation.domain.anonymization.service.AnonymizationCheckersService; -import org.folio.circulation.domain.anonymization.service.LoanAnonymizationFinderService; -import org.folio.circulation.domain.anonymization.service.LoansForBorrowerFinder; -import org.folio.circulation.domain.anonymization.service.LoansForTenantFinder; -import org.folio.circulation.infrastructure.storage.feesandfines.AccountRepository; -import org.folio.circulation.infrastructure.storage.loans.AnonymizeStorageLoansRepository; -import org.folio.circulation.infrastructure.storage.loans.LoanRepository; -import org.folio.circulation.services.EventPublisher; -import org.folio.circulation.support.Clients; -import org.folio.circulation.support.http.client.PageLimit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class LoanAnonymization { - private final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - public static final PageLimit FETCH_LOANS_PAGE_LIMIT = limit(5000); - - private final LoanRepository loanRepository; - private final AccountRepository accountRepository; - private final AnonymizeStorageLoansRepository anonymizeStorageLoansRepository; - private final EventPublisher eventPublisher; - - public LoanAnonymization(Clients clients, LoanRepository loanRepository, - AccountRepository accountRepository) { - - this.loanRepository = loanRepository; - this.accountRepository = accountRepository; - anonymizeStorageLoansRepository = new AnonymizeStorageLoansRepository(clients); - eventPublisher = new EventPublisher(clients.pubSubPublishingService()); - } - - public LoanAnonymizationService byUserId(String userId) { - log.info("Initializing loan anonymization for borrower"); - - return createService(new AnonymizationCheckersService(), - new LoansForBorrowerFinder(userId, loanRepository, accountRepository)); - } - - public LoanAnonymizationService byCurrentTenant(LoanAnonymizationConfiguration config) { - log.info("Initializing loan anonymization for current tenant"); - - if (neverAnonymizeLoans(config)) { - return new NeverLoanAnonymizationService(); - } - - return createService(new AnonymizationCheckersService(config), - new LoansForTenantFinder(loanRepository, accountRepository)); - } - - private boolean neverAnonymizeLoans(LoanAnonymizationConfiguration config) { - return config.getLoanClosingType() == ClosingType.NEVER && - !config.treatLoansWithFeesAndFinesDifferently(); - } - - private DefaultLoanAnonymizationService createService( - AnonymizationCheckersService anonymizationCheckersService, - LoanAnonymizationFinderService loansFinderService) { - - return new DefaultLoanAnonymizationService(anonymizationCheckersService, - loansFinderService, anonymizeStorageLoansRepository, eventPublisher); - } -} diff --git a/src/main/java/org/folio/circulation/domain/anonymization/LoanAnonymizationService.java b/src/main/java/org/folio/circulation/domain/anonymization/LoanAnonymizationService.java index bc0a4b125c..a60fc09b1b 100644 --- a/src/main/java/org/folio/circulation/domain/anonymization/LoanAnonymizationService.java +++ b/src/main/java/org/folio/circulation/domain/anonymization/LoanAnonymizationService.java @@ -1,9 +1,13 @@ package org.folio.circulation.domain.anonymization; +import java.util.Collection; import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import org.folio.circulation.domain.Loan; import org.folio.circulation.support.results.Result; public interface LoanAnonymizationService { - CompletableFuture> anonymizeLoans(); + CompletableFuture> anonymizeLoans( + Supplier>>> loansToCheck); } diff --git a/src/main/java/org/folio/circulation/domain/anonymization/NeverLoanAnonymizationService.java b/src/main/java/org/folio/circulation/domain/anonymization/NeverLoanAnonymizationService.java deleted file mode 100644 index 8144f2c128..0000000000 --- a/src/main/java/org/folio/circulation/domain/anonymization/NeverLoanAnonymizationService.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.folio.circulation.domain.anonymization; - -import static java.util.concurrent.CompletableFuture.completedFuture; - -import java.util.concurrent.CompletableFuture; - -import org.folio.circulation.support.results.Result; - -public class NeverLoanAnonymizationService implements LoanAnonymizationService { - @Override - public CompletableFuture> anonymizeLoans() { - return completedFuture(Result.of(LoanAnonymizationRecords::new)); - } -} diff --git a/src/main/java/org/folio/circulation/domain/anonymization/service/AnonymizationCheckersService.java b/src/main/java/org/folio/circulation/domain/anonymization/service/AnonymizationCheckersService.java index 4d7b5d57d8..b59a227c5b 100644 --- a/src/main/java/org/folio/circulation/domain/anonymization/service/AnonymizationCheckersService.java +++ b/src/main/java/org/folio/circulation/domain/anonymization/service/AnonymizationCheckersService.java @@ -17,6 +17,7 @@ import org.folio.circulation.domain.anonymization.checkers.NeverAnonymizeLoansChecker; import org.folio.circulation.domain.anonymization.checkers.NeverAnonymizeLoansWithFeeFinesChecker; import org.folio.circulation.domain.anonymization.checkers.NoAssociatedFeesAndFinesChecker; +import org.folio.circulation.domain.anonymization.config.ClosingType; import org.folio.circulation.domain.anonymization.config.LoanAnonymizationConfiguration; public class AnonymizationCheckersService { @@ -26,7 +27,6 @@ public class AnonymizationCheckersService { private AnonymizationChecker feesAndFinesCheckersFromLoanHistory; private AnonymizationChecker closedLoansCheckersFromLoanHistory; - public AnonymizationCheckersService(LoanAnonymizationConfiguration config) { this.config = config; if ( config != null) { @@ -40,6 +40,16 @@ public AnonymizationCheckersService() { this(null); } + public boolean neverAnonymizeLoans() { + // Without config, this cannot be determined + if (config == null) { + return false; + } + + return config.getLoanClosingType() == ClosingType.NEVER && + !config.treatLoansWithFeesAndFinesDifferently(); + } + public Map> segregateLoans(Collection loans) { return loans.stream() .collect(Collectors.groupingBy(applyCheckersForLoanAndLoanHistoryConfig(), diff --git a/src/main/java/org/folio/circulation/domain/anonymization/service/DefaultLoansFinder.java b/src/main/java/org/folio/circulation/domain/anonymization/service/DefaultLoansFinder.java index 9515aff4db..df7cdb76c7 100644 --- a/src/main/java/org/folio/circulation/domain/anonymization/service/DefaultLoansFinder.java +++ b/src/main/java/org/folio/circulation/domain/anonymization/service/DefaultLoansFinder.java @@ -10,7 +10,7 @@ import org.folio.circulation.infrastructure.storage.feesandfines.AccountRepository; import org.folio.circulation.support.results.Result; -abstract class DefaultLoansFinder implements LoanAnonymizationFinderService { +abstract class DefaultLoansFinder { private final AccountRepository accountRepository; DefaultLoansFinder(AccountRepository accountRepository) { diff --git a/src/main/java/org/folio/circulation/domain/anonymization/service/LoanAnonymizationFinderService.java b/src/main/java/org/folio/circulation/domain/anonymization/service/LoanAnonymizationFinderService.java deleted file mode 100644 index 760cb41a9f..0000000000 --- a/src/main/java/org/folio/circulation/domain/anonymization/service/LoanAnonymizationFinderService.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.folio.circulation.domain.anonymization.service; - -import java.util.Collection; -import java.util.concurrent.CompletableFuture; - -import org.folio.circulation.domain.Loan; -import org.folio.circulation.support.results.Result; - -public interface LoanAnonymizationFinderService { - CompletableFuture>> findLoansToAnonymize(); -} diff --git a/src/main/java/org/folio/circulation/domain/anonymization/service/LoansForBorrowerFinder.java b/src/main/java/org/folio/circulation/domain/anonymization/service/LoansForBorrowerFinder.java index eac28427bc..48cd6501a0 100644 --- a/src/main/java/org/folio/circulation/domain/anonymization/service/LoansForBorrowerFinder.java +++ b/src/main/java/org/folio/circulation/domain/anonymization/service/LoansForBorrowerFinder.java @@ -1,6 +1,6 @@ package org.folio.circulation.domain.anonymization.service; -import static org.folio.circulation.domain.anonymization.LoanAnonymization.FETCH_LOANS_PAGE_LIMIT; +import static org.folio.circulation.support.http.client.PageLimit.limit; import java.util.Collection; import java.util.concurrent.CompletableFuture; @@ -22,9 +22,8 @@ public LoansForBorrowerFinder(String userId, LoanRepository loanRepository, this.loanRepository = loanRepository; } - @Override public CompletableFuture>> findLoansToAnonymize() { - return loanRepository.findClosedLoans(userId, FETCH_LOANS_PAGE_LIMIT) + return loanRepository.findClosedLoans(userId, limit(5000)) .thenCompose(this::fetchAdditionalLoanInfo); } } diff --git a/src/main/java/org/folio/circulation/domain/anonymization/service/LoansForTenantFinder.java b/src/main/java/org/folio/circulation/domain/anonymization/service/LoansForTenantFinder.java index 2c34838229..ee1dfdd255 100644 --- a/src/main/java/org/folio/circulation/domain/anonymization/service/LoansForTenantFinder.java +++ b/src/main/java/org/folio/circulation/domain/anonymization/service/LoansForTenantFinder.java @@ -1,6 +1,6 @@ package org.folio.circulation.domain.anonymization.service; -import static org.folio.circulation.domain.anonymization.LoanAnonymization.FETCH_LOANS_PAGE_LIMIT; +import static org.folio.circulation.support.http.client.PageLimit.limit; import java.util.Collection; import java.util.concurrent.CompletableFuture; @@ -12,17 +12,18 @@ public class LoansForTenantFinder extends DefaultLoansFinder { private final LoanRepository loanRepository; + private final int numberOfLoansToCheck; public LoansForTenantFinder(LoanRepository loanRepository, - AccountRepository accountRepository) { + AccountRepository accountRepository, int numberOfLoansToCheck) { super(accountRepository); this.loanRepository = loanRepository; + this.numberOfLoansToCheck = numberOfLoansToCheck; } - @Override public CompletableFuture>> findLoansToAnonymize() { - return loanRepository.findLoansToAnonymize(FETCH_LOANS_PAGE_LIMIT) + return loanRepository.findLoansToAnonymize(limit(numberOfLoansToCheck)) .thenCompose(this::fetchAdditionalLoanInfo); } } diff --git a/src/main/java/org/folio/circulation/resources/LoanAnonymizationResource.java b/src/main/java/org/folio/circulation/resources/LoanAnonymizationResource.java index 247d99320d..f7b570ef14 100644 --- a/src/main/java/org/folio/circulation/resources/LoanAnonymizationResource.java +++ b/src/main/java/org/folio/circulation/resources/LoanAnonymizationResource.java @@ -1,11 +1,17 @@ package org.folio.circulation.resources; -import static java.util.concurrent.CompletableFuture.completedFuture; +import java.lang.invoke.MethodHandles; -import org.folio.circulation.domain.anonymization.LoanAnonymization; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.circulation.domain.anonymization.DefaultLoanAnonymizationService; +import org.folio.circulation.domain.anonymization.service.AnonymizationCheckersService; +import org.folio.circulation.domain.anonymization.service.LoansForBorrowerFinder; import org.folio.circulation.domain.representations.anonymization.AnonymizeLoansRepresentation; import org.folio.circulation.infrastructure.storage.feesandfines.AccountRepository; +import org.folio.circulation.infrastructure.storage.loans.AnonymizeStorageLoansRepository; import org.folio.circulation.infrastructure.storage.loans.LoanRepository; +import org.folio.circulation.services.EventPublisher; import org.folio.circulation.support.Clients; import org.folio.circulation.support.RouteRegistration; import org.folio.circulation.support.http.server.JsonHttpResponse; @@ -16,6 +22,8 @@ import io.vertx.ext.web.RoutingContext; public class LoanAnonymizationResource extends Resource { + private final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); + public LoanAnonymizationResource(HttpClient client) { super(client); } @@ -32,12 +40,23 @@ private void anonymizeLoans(RoutingContext routingContext) { String borrowerId = routingContext.request().getParam("userId"); - final var loanAnonymization = new LoanAnonymization(clients, new LoanRepository(clients), new AccountRepository(clients)); + final var loanRepository = new LoanRepository(clients); + final var accountRepository = new AccountRepository(clients); + + final var loansFinder = new LoansForBorrowerFinder(borrowerId, + loanRepository, accountRepository); + + final var anonymizeStorageLoansRepository = new AnonymizeStorageLoansRepository(clients); + final var eventPublisher = new EventPublisher(clients.pubSubPublishingService()); + + final var loanAnonymizationService = new DefaultLoanAnonymizationService( + new AnonymizationCheckersService(), anonymizeStorageLoansRepository, eventPublisher); + + log.info("Initializing loan anonymization for borrower: {}", borrowerId); - completedFuture(loanAnonymization.byUserId(borrowerId) - .anonymizeLoans() + loanAnonymizationService.anonymizeLoans(loansFinder::findLoansToAnonymize) .thenApply(AnonymizeLoansRepresentation::from) .thenApply(r -> r.map(JsonHttpResponse::ok)) - .thenAccept(context::writeResultToHttpResponse)); + .thenAccept(context::writeResultToHttpResponse); } } diff --git a/src/main/java/org/folio/circulation/resources/ScheduledAnonymizationProcessingResource.java b/src/main/java/org/folio/circulation/resources/ScheduledAnonymizationProcessingResource.java index 355c5c431f..485cb93c5a 100644 --- a/src/main/java/org/folio/circulation/resources/ScheduledAnonymizationProcessingResource.java +++ b/src/main/java/org/folio/circulation/resources/ScheduledAnonymizationProcessingResource.java @@ -2,11 +2,20 @@ import static org.folio.circulation.support.results.AsynchronousResultBindings.safelyInitialise; -import org.folio.circulation.domain.anonymization.LoanAnonymization; +import java.lang.invoke.MethodHandles; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.Environment; +import org.folio.circulation.domain.anonymization.DefaultLoanAnonymizationService; +import org.folio.circulation.domain.anonymization.service.AnonymizationCheckersService; +import org.folio.circulation.domain.anonymization.service.LoansForTenantFinder; import org.folio.circulation.domain.representations.anonymization.AnonymizeLoansRepresentation; import org.folio.circulation.infrastructure.storage.ConfigurationRepository; import org.folio.circulation.infrastructure.storage.feesandfines.AccountRepository; +import org.folio.circulation.infrastructure.storage.loans.AnonymizeStorageLoansRepository; import org.folio.circulation.infrastructure.storage.loans.LoanRepository; +import org.folio.circulation.services.EventPublisher; import org.folio.circulation.support.Clients; import org.folio.circulation.support.RouteRegistration; import org.folio.circulation.support.http.server.JsonHttpResponse; @@ -23,6 +32,8 @@ * */ public class ScheduledAnonymizationProcessingResource extends Resource { + private final Logger log = LogManager.getLogger(MethodHandles.lookup().lookupClass()); + public ScheduledAnonymizationProcessingResource(HttpClient client) { super(client); } @@ -38,12 +49,21 @@ private void scheduledAnonymizeLoans(RoutingContext routingContext) { final Clients clients = Clients.create(context, client); ConfigurationRepository configurationRepository = new ConfigurationRepository(clients); - LoanAnonymization loanAnonymization = new LoanAnonymization(clients, - new LoanRepository(clients), new AccountRepository(clients)); + final var loanRepository = new LoanRepository(clients); + final var accountRepository = new AccountRepository(clients); + + final var anonymizeStorageLoansRepository = new AnonymizeStorageLoansRepository(clients); + final var eventPublisher = new EventPublisher(clients.pubSubPublishingService()); + + final var loansFinder = new LoansForTenantFinder(loanRepository, accountRepository, + Environment.getScheduledAnonymizationNumberOfLoansToCheck()); + + log.info("Initializing loan anonymization for current tenant"); safelyInitialise(configurationRepository::loanHistoryConfiguration) - .thenCompose(r -> r.after(config -> loanAnonymization - .byCurrentTenant(config).anonymizeLoans())) + .thenApply(r -> r.map(config -> new DefaultLoanAnonymizationService( + new AnonymizationCheckersService(config), anonymizeStorageLoansRepository, eventPublisher))) + .thenCompose(r -> r.after(service -> service.anonymizeLoans(loansFinder::findLoansToAnonymize))) .thenApply(AnonymizeLoansRepresentation::from) .thenApply(r -> r.map(JsonHttpResponse::ok)) .exceptionally(CommonFailures::failedDueToServerError) diff --git a/src/test/java/org/folio/circulation/domain/anonymization/LoanAnonymizationServiceTests.java b/src/test/java/org/folio/circulation/domain/anonymization/LoanAnonymizationServiceTests.java new file mode 100644 index 0000000000..835f977117 --- /dev/null +++ b/src/test/java/org/folio/circulation/domain/anonymization/LoanAnonymizationServiceTests.java @@ -0,0 +1,143 @@ +package org.folio.circulation.domain.anonymization; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.folio.circulation.domain.anonymization.config.ClosingType.IMMEDIATELY; +import static org.folio.circulation.domain.anonymization.config.ClosingType.NEVER; +import static org.folio.circulation.support.json.JsonPropertyWriter.write; +import static org.folio.circulation.support.json.JsonPropertyWriter.writeByPath; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import org.folio.circulation.domain.Loan; +import org.folio.circulation.domain.anonymization.config.ClosingType; +import org.folio.circulation.domain.anonymization.config.LoanAnonymizationConfiguration; +import org.folio.circulation.domain.anonymization.service.AnonymizationCheckersService; +import org.folio.circulation.domain.anonymization.service.LoansForTenantFinder; +import org.folio.circulation.infrastructure.storage.loans.AnonymizeStorageLoansRepository; +import org.folio.circulation.services.EventPublisher; +import org.folio.circulation.support.results.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; + +import io.vertx.core.json.JsonObject; +import lombok.SneakyThrows; + +public class LoanAnonymizationServiceTests { + @Mock + AnonymizeStorageLoansRepository anonymizeStorageLoansRepository; + @Mock + EventPublisher eventPublisher; + @Mock + LoansForTenantFinder loansForTenantFinder; + + @BeforeEach + public void init() { + initMocks(this); + } + + @SneakyThrows + @Test + void shouldAnonymizeLoansImmediatelyWhenConfiguredToDoSo() { + final var config = anonymizeLoans(IMMEDIATELY); + + final var service = createService(config); + + final var loanToAnonymize = singleClosedLoanWithNoFeesFines(); + + final var finished + = service.anonymizeLoans(loansForTenantFinder::findLoansToAnonymize); + + finished.get(1, SECONDS); + + final var anonymizedLoansCaptor + = ArgumentCaptor.forClass(LoanAnonymizationRecords.class); + + verify(anonymizeStorageLoansRepository, times(1)) + .postAnonymizeStorageLoans(anonymizedLoansCaptor.capture()); + + assertThat(anonymizedLoansCaptor.getValue().getAnonymizedLoanIds(), + containsInAnyOrder(loanToAnonymize.getId())); + + verify(loansForTenantFinder, times(1)).findLoansToAnonymize(); + + verifyNoMoreInteractions(loansForTenantFinder); + verifyNoMoreInteractions(anonymizeStorageLoansRepository); + } + + @SneakyThrows + @Test + void shouldNeverAnonymizeLoans() { + final var config = anonymizeLoans(NEVER); + + final var service = createService(config); + + singleClosedLoanWithNoFeesFines(); + + final var finished + = service.anonymizeLoans(loansForTenantFinder::findLoansToAnonymize); + + finished.get(1, SECONDS); + + verify(loansForTenantFinder, times(0)).findLoansToAnonymize(); + + verify(anonymizeStorageLoansRepository, times(0)) + .postAnonymizeStorageLoans(any()); + + verifyNoMoreInteractions(loansForTenantFinder); + verifyNoMoreInteractions(anonymizeStorageLoansRepository); + } + + private Loan singleClosedLoanWithNoFeesFines() { + final var loan = fakeLoan(); + + final CompletableFuture>> loans = completedFuture( + Result.of(() -> List.of(loan))); + + when(loansForTenantFinder.findLoansToAnonymize()).thenReturn(loans); + + return loan; + } + + private Loan fakeLoan() { + final var json = new JsonObject(); + + write(json, "id", UUID.randomUUID()); + writeByPath(json, "Closed", "status", "name"); + + return Loan.from(json); + } + + private LoanAnonymizationConfiguration anonymizeLoans(ClosingType loansClosingType) { + final var json = new JsonObject(); + + final var closingType = new JsonObject(); + write(closingType, "loan", loansClosingType.getRepresentation()); + write(json, "closingType", closingType); + + //Whether to treat loans with fees/fines differently + write(json, "treatEnabled", false); + + return LoanAnonymizationConfiguration.from(json); + } + + private LoanAnonymizationService createService(LoanAnonymizationConfiguration config) { + final var anonymizationCheckersService = new AnonymizationCheckersService(config); + + return new DefaultLoanAnonymizationService(anonymizationCheckersService, + anonymizeStorageLoansRepository, eventPublisher); + } +} diff --git a/src/test/java/org/folio/circulation/domain/anonymization/LoanAnonymizationTests.java b/src/test/java/org/folio/circulation/domain/anonymization/LoanAnonymizationTests.java deleted file mode 100644 index f2def4d0c1..0000000000 --- a/src/test/java/org/folio/circulation/domain/anonymization/LoanAnonymizationTests.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.folio.circulation.domain.anonymization; - -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.folio.circulation.domain.anonymization.config.ClosingType.IMMEDIATELY; -import static org.folio.circulation.domain.anonymization.config.ClosingType.NEVER; -import static org.folio.circulation.support.json.JsonPropertyWriter.write; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; - -import java.util.ArrayList; -import java.util.UUID; - -import org.folio.circulation.domain.Loan; -import org.folio.circulation.domain.MultipleRecords; -import org.folio.circulation.domain.anonymization.config.ClosingType; -import org.folio.circulation.domain.anonymization.config.LoanAnonymizationConfiguration; -import org.folio.circulation.infrastructure.storage.feesandfines.AccountRepository; -import org.folio.circulation.infrastructure.storage.loans.LoanRepository; -import org.folio.circulation.support.Clients; -import org.folio.circulation.support.results.Result; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; - -import io.vertx.core.json.JsonObject; -import lombok.SneakyThrows; - -public class LoanAnonymizationTests { - @Mock - Clients clients; - @Mock - LoanRepository loanRepository; - @Mock - AccountRepository accountRepository; - - @BeforeEach - public void init() { - initMocks(this); - } - - @SneakyThrows - @Test - void shouldAnonymizeLoansImmediatelyWhenConfiguredToDoSo() { - final var config = anonymizeLoans(IMMEDIATELY); - - final var loanAnonymization = new LoanAnonymization(clients, loanRepository, - accountRepository); - - final var service = loanAnonymization.byCurrentTenant(config); - - singleLoanToAnonymize(); - - final var finished = service.anonymizeLoans(); - - finished.get(1, SECONDS); - - verify(loanRepository, times(1)).findLoansToAnonymize(any()); - verifyNoMoreInteractions(loanRepository); - - verify(accountRepository, times(1)).findAccountsForLoans(any()); - verifyNoMoreInteractions(accountRepository); - } - - @SneakyThrows - @Test - void shouldNeverAnonymizeLoans() { - final var config = anonymizeLoans(NEVER); - - final var loanAnonymization = new LoanAnonymization(clients, loanRepository, - accountRepository); - - final var service = loanAnonymization.byCurrentTenant(config); - - final var finished = service.anonymizeLoans(); - - finished.get(1, SECONDS); - - verifyNoMoreInteractions(loanRepository); - verifyNoMoreInteractions(accountRepository); - } - - private void singleLoanToAnonymize() { - final var loans = new ArrayList(); - - loans.add(fakeLoan()); - - when(loanRepository.findLoansToAnonymize(any())) - .thenReturn(completedFuture(Result.of(() -> new MultipleRecords<>(loans, 1)))); - } - - private Loan fakeLoan() { - return Loan.from(new JsonObject().put("id", UUID.randomUUID().toString())); - } - - private LoanAnonymizationConfiguration anonymizeLoans(ClosingType loansClosingType) { - final var json = new JsonObject(); - - final var closingType = new JsonObject(); - write(closingType, "loan", loansClosingType.getRepresentation()); - write(json, "closingType", closingType); - - //Whether to treat loans with fees/fines differently - write(json, "treatEnabled", false); - - return LoanAnonymizationConfiguration.from(json); - } -}