FINERACT-1977: 4-eye-principle, maker-checker permission

This commit is contained in:
jmarta
2024-01-11 11:46:32 +01:00
committed by Adam Saghy
parent 2fe1b0e7d3
commit 16eac933d6
69 changed files with 887 additions and 359 deletions
@@ -52,7 +52,6 @@ import org.apache.fineract.batch.exception.BatchReferenceInvalidException;
import org.apache.fineract.batch.exception.ErrorInfo;
import org.apache.fineract.batch.service.ResolutionHelper.BatchRequestNode;
import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder;
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
import org.apache.fineract.infrastructure.core.filters.BatchFilter;
@@ -62,10 +61,8 @@ import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionExecution;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.TransactionTemplate;
@@ -104,8 +101,7 @@ public class BatchApiServiceImpl implements BatchApiService {
*/
@Override
public List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> requestList, UriInfo uriInfo) {
BatchRequestContextHolder.setEnclosingTransaction(Optional.empty());
return handleBatchRequests(false, requestList, uriInfo);
return handleBatchRequests(requestList, uriInfo, false);
}
/**
@@ -117,7 +113,18 @@ public class BatchApiServiceImpl implements BatchApiService {
*/
@Override
public List<BatchResponse> handleBatchRequestsWithEnclosingTransaction(final List<BatchRequest> requestList, final UriInfo uriInfo) {
return callInTransaction(Function.identity()::apply, () -> handleBatchRequests(true, requestList, uriInfo));
return handleBatchRequests(requestList, uriInfo, true);
}
private List<BatchResponse> handleBatchRequests(final List<BatchRequest> requestList, final UriInfo uriInfo,
boolean enclosingTransaction) {
BatchRequestContextHolder.setIsEnclosingTransaction(enclosingTransaction);
try {
return enclosingTransaction ? callInTransaction(Function.identity()::apply, () -> handleRequestNodes(requestList, uriInfo))
: handleRequestNodes(requestList, uriInfo);
} finally {
BatchRequestContextHolder.resetIsEnclosingTransaction();
}
}
/**
@@ -136,18 +143,15 @@ public class BatchApiServiceImpl implements BatchApiService {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionConfigurator.accept(transactionTemplate);
return transactionTemplate.execute(status -> {
BatchRequestContextHolder.setEnclosingTransaction(Optional.of(status));
BatchRequestContextHolder.setEnclosingTransaction(status);
try {
responseList.addAll(request.get());
return responseList;
} catch (BatchExecutionException ex) {
status.setRollbackOnly();
return List.of(buildErrorResponse(ex.getCause(), ex.getRequest()));
} catch (RuntimeException ex) {
status.setRollbackOnly();
return buildErrorResponses(ex, responseList);
responseList.add(buildErrorResponse(ex.getCause(), ex.getRequest()));
return responseList;
} finally {
BatchRequestContextHolder.setEnclosingTransaction(Optional.empty());
BatchRequestContextHolder.resetTransaction();
}
});
} catch (TransactionException | NonTransientDataAccessException ex) {
@@ -163,24 +167,17 @@ public class BatchApiServiceImpl implements BatchApiService {
* @param uriInfo
* @return {@code List<BatchResponse>}
*/
private List<BatchResponse> handleBatchRequests(boolean enclosingTransaction, final List<BatchRequest> requestList,
final UriInfo uriInfo) {
private List<BatchResponse> handleRequestNodes(final List<BatchRequest> requestList, final UriInfo uriInfo) {
final List<BatchRequestNode> rootNodes;
try {
rootNodes = this.resolutionHelper.buildNodesTree(requestList);
} catch (BatchReferenceInvalidException e) {
return List.of(buildErrorResponse(e));
return List.of(buildOrThrowErrorResponse(e, null));
}
final ArrayList<BatchResponse> responseList = new ArrayList<>(requestList.size());
for (BatchRequestNode rootNode : rootNodes) {
if (enclosingTransaction) {
this.callRequestRecursive(rootNode.getRequest(), rootNode, responseList, uriInfo, enclosingTransaction);
} else {
ArrayList<BatchResponse> localResponseList = new ArrayList<>();
this.callRequestRecursive(rootNode.getRequest(), rootNode, localResponseList, uriInfo, enclosingTransaction);
responseList.addAll(localResponseList);
}
this.callRequestRecursive(rootNode.getRequest(), rootNode, responseList, uriInfo);
}
responseList.sort(Comparator.comparing(BatchResponse::getRequestId));
return responseList;
@@ -197,18 +194,10 @@ public class BatchApiServiceImpl implements BatchApiService {
* the collected responses
* @return {@code BatchResponse}
*/
private void callRequestRecursive(BatchRequest request, BatchRequestNode requestNode, List<BatchResponse> responseList, UriInfo uriInfo,
boolean enclosingTransaction) {
private void callRequestRecursive(BatchRequest request, BatchRequestNode requestNode, List<BatchResponse> responseList,
UriInfo uriInfo) {
// run current node
BatchResponse response;
if (enclosingTransaction) {
response = executeRequest(request, uriInfo);
} else {
List<BatchResponse> transactionResponse = callInTransaction(
transactionTemplate -> transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
() -> List.of(executeRequest(request, uriInfo)));
response = transactionResponse.get(0);
}
BatchResponse response = executeRequest(request, uriInfo);
responseList.add(response);
if (response.getStatusCode() != null && response.getStatusCode() == SC_OK) {
// run child nodes
@@ -217,13 +206,10 @@ public class BatchApiServiceImpl implements BatchApiService {
BatchRequest resolvedChildRequest;
try {
resolvedChildRequest = this.resolutionHelper.resolveRequest(childRequest, response);
callRequestRecursive(resolvedChildRequest, childNode, responseList, uriInfo);
} catch (JsonPathException jpex) {
responseList.add(buildErrorResponse(jpex, childRequest));
return;
} catch (RuntimeException ex) {
throw new BatchExecutionException(childRequest, ex);
responseList.add(buildOrThrowErrorResponse(jpex, childRequest));
}
callRequestRecursive(resolvedChildRequest, childNode, responseList, uriInfo, enclosingTransaction);
});
} else {
responseList.addAll(parentRequestFailedRecursive(request, requestNode, response, null));
@@ -245,7 +231,7 @@ public class BatchApiServiceImpl implements BatchApiService {
log.debug("Batch request: method [{}], relative url [{}]", request.getMethod(), request.getRelativeUrl());
Either<RuntimeException, BatchRequest> preprocessorResult = runPreprocessors(request);
if (preprocessorResult.isLeft()) {
throw new BatchExecutionException(request, preprocessorResult.getLeft());
return buildOrThrowErrorResponse(preprocessorResult.getLeft(), request);
} else {
request = preprocessorResult.get();
}
@@ -253,19 +239,16 @@ public class BatchApiServiceImpl implements BatchApiService {
BatchRequestContextHolder.setRequestAttributes(new HashMap<>(Optional.ofNullable(request.getHeaders())
.map(list -> list.stream().collect(Collectors.toMap(Header::getName, Header::getValue)))
.orElse(Collections.emptyMap())));
BatchCallHandler callHandler = new BatchCallHandler(this.batchFilters, commandStrategy::execute);
Optional<TransactionStatus> transaction = BatchRequestContextHolder.getEnclosingTransaction();
if (transaction.isPresent()) {
if (BatchRequestContextHolder.isEnclosingTransaction()) {
entityManager.flush();
}
BatchCallHandler callHandler = new BatchCallHandler(this.batchFilters, commandStrategy::execute);
final BatchResponse rootResponse = callHandler.serviceCall(request, uriInfo);
log.debug("Batch response: status code [{}], method [{}], relative url [{}]", rootResponse.getStatusCode(), request.getMethod(),
request.getRelativeUrl());
return rootResponse;
} catch (AbstractIdempotentCommandException idempotentException) {
return buildErrorResponse(idempotentException, request);
} catch (RuntimeException ex) {
throw new BatchExecutionException(request, ex);
return buildOrThrowErrorResponse(ex, request);
} finally {
BatchRequestContextHolder.resetRequestAttributes();
}
@@ -313,11 +296,6 @@ public class BatchApiServiceImpl implements BatchApiService {
return responseList;
}
@NotNull
private BatchResponse buildErrorResponse(@NotNull Throwable ex) {
return buildErrorResponse(ex, null);
}
/**
* Return the response when any exception raised
*
@@ -346,6 +324,15 @@ public class BatchApiServiceImpl implements BatchApiService {
return buildErrorResponse(requestId, statusCode, body, headers);
}
private BatchResponse buildOrThrowErrorResponse(RuntimeException ex, BatchRequest request) {
BatchResponse response = buildErrorResponse(ex, request);
if (response.getStatusCode() != SC_OK && BatchRequestContextHolder.isEnclosingTransaction()) {
BatchRequestContextHolder.getTransaction().ifPresent(TransactionExecution::setRollbackOnly);
throw new BatchExecutionException(request, ex);
}
return response;
}
@NotNull
private List<BatchResponse> buildErrorResponses(Throwable ex, @NotNull List<BatchResponse> responseList) {
BatchResponse response = responseList.isEmpty() ? null
@@ -368,9 +355,10 @@ public class BatchApiServiceImpl implements BatchApiService {
}
if (response != null) {
requestId = response.getRequestId();
if (response.getStatusCode() == null || response.getStatusCode() != SC_OK) {
if (response.getStatusCode() != null) {
statusCode = response.getStatusCode();
Integer responseCode = response.getStatusCode();
if (responseCode == null || responseCode != SC_OK) {
if (responseCode != null) {
statusCode = responseCode;
}
body = "Transaction is being rolled back. First erroneous request: \n" + new Gson().toJson(response);
}
@@ -202,6 +202,14 @@ public class CommandSource extends AbstractPersistableCustom {
this.commandAsJson = json;
}
public AppUser getMaker() {
return maker;
}
public AppUser getChecker() {
return checker;
}
public String getActionName() {
return this.actionName;
}
@@ -1,42 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.commands.exception;
import org.apache.fineract.commands.domain.CommandSource;
public class RollbackTransactionAsCommandIsNotApprovedByCheckerException extends RuntimeException {
/**
* When maker-checker is configured globally and also for the current transaction.
*
* An initial save determines if there are any integrity rule or data problems.
*
* If there isn't... and the transaction is from a maker... then this roll back is issued and the
* commandSourceResult is used to write the audit entry.
*/
private final CommandSource commandSourceResult;
public RollbackTransactionAsCommandIsNotApprovedByCheckerException(final CommandSource commandSourceResult) {
this.commandSourceResult = commandSourceResult;
}
public CommandSource getCommandSourceResult() {
return this.commandSourceResult;
}
}
@@ -0,0 +1,41 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.commands.exception;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
/**
* When maker-checker is configured globally and also for the current transaction. An initial save determines if there
* are any integrity rule or data problems. If there isn't... and the transaction is from a maker... then this roll back
* is issued and the commandSourceResult is used to write the audit entry.
*/
public class RollbackTransactionNotApprovedException extends RuntimeException {
private final CommandProcessingResult result;
public RollbackTransactionNotApprovedException(Long commandId, Long entityId) {
this.result = new CommandProcessingResultBuilder().withCommandId(commandId).withEntityId(entityId).setRollbackTransaction(true)
.build();
}
public CommandProcessingResult getResult() {
return result;
}
}
@@ -30,6 +30,11 @@ public class UnsupportedCommandException extends RuntimeException {
this.unsupportedCommandName = unsupportedCommandName;
}
public UnsupportedCommandException(final String unsupportedCommandName, String message) {
super(message);
this.unsupportedCommandName = unsupportedCommandName;
}
public String getUnsupportedCommandName() {
return this.unsupportedCommandName;
}
@@ -18,7 +18,6 @@
*/
package org.apache.fineract.commands.service;
import org.apache.fineract.commands.domain.CommandSource;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
@@ -28,8 +27,6 @@ public interface CommandProcessingService {
CommandProcessingResult executeCommand(CommandWrapper wrapper, JsonCommand command, boolean isApprovedByChecker);
CommandProcessingResult logCommand(CommandSource commandSourceResult);
boolean validateCommand(CommandWrapper commandWrapper, AppUser user);
boolean validateRollbackCommand(CommandWrapper commandWrapper, AppUser user);
}
@@ -26,7 +26,10 @@ import org.apache.fineract.commands.domain.CommandSource;
import org.apache.fineract.commands.domain.CommandSourceRepository;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.exception.CommandNotFoundException;
import org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.useradministration.domain.AppUser;
import org.jetbrains.annotations.NotNull;
@@ -95,19 +98,24 @@ public class CommandSourceService {
idempotencyKey);
}
@Transactional(propagation = Propagation.REQUIRED)
public CommandSource getInitialCommandSource(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
CommandSource commandSourceResult;
if (jsonCommand.commandId() != null) {
commandSourceResult = commandSourceRepository.findById(jsonCommand.commandId())
.orElseThrow(() -> new CommandNotFoundException(jsonCommand.commandId()));
commandSourceResult.markAsChecked(maker);
} else {
commandSourceResult = CommandSource.fullEntryFrom(wrapper, jsonCommand, maker, idempotencyKey, UNDER_PROCESSING.getValue());
}
CommandSource commandSourceResult = CommandSource.fullEntryFrom(wrapper, jsonCommand, maker, idempotencyKey,
UNDER_PROCESSING.getValue());
if (commandSourceResult.getCommandJson() == null) {
commandSourceResult.setCommandJson("{}");
}
return commandSourceResult;
}
@Transactional
public CommandProcessingResult processCommand(NewCommandSourceHandler handler, JsonCommand command, CommandSource commandSource,
AppUser user, boolean isApprovedByChecker, boolean isMakerChecker) {
final CommandProcessingResult result = handler.processCommand(command);
boolean isRollback = !isApprovedByChecker && !user.isCheckerSuperUser() && (isMakerChecker || result.isRollbackTransaction());
if (isRollback) {
commandSource.markAsAwaitingApproval();
throw new RollbackTransactionNotApprovedException(commandSource.getId(), commandSource.getResourceId());
}
return result;
}
}
@@ -19,6 +19,7 @@
package org.apache.fineract.commands.service;
import com.google.gson.JsonElement;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.commands.domain.CommandSource;
@@ -26,6 +27,8 @@ import org.apache.fineract.commands.domain.CommandSourceRepository;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.exception.CommandNotAwaitingApprovalException;
import org.apache.fineract.commands.exception.CommandNotFoundException;
import org.apache.fineract.commands.exception.UnsupportedCommandException;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
@@ -45,10 +48,10 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
private final FromJsonHelper fromApiJsonHelper;
private final CommandProcessingService processAndLogCommandService;
private final SchedulerJobRunnerReadService schedulerJobRunnerReadService;
private final ConfigurationDomainService configurationService;
@Override
public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) {
boolean isApprovedByChecker = false;
// check if is update of own account details
@@ -77,7 +80,6 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
@Override
public CommandProcessingResult approveEntry(final Long makerCheckerId) {
final CommandSource commandSourceInput = validateMakerCheckerTransaction(makerCheckerId);
validateIsUpdateAllowed();
@@ -111,16 +113,24 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
}
private CommandSource validateMakerCheckerTransaction(final Long makerCheckerId) {
final CommandSource commandSourceInput = this.commandSourceRepository.findById(makerCheckerId)
final CommandSource commandSource = this.commandSourceRepository.findById(makerCheckerId)
.orElseThrow(() -> new CommandNotFoundException(makerCheckerId));
if (!commandSourceInput.isMarkedAsAwaitingApproval()) {
if (!commandSource.isMarkedAsAwaitingApproval()) {
throw new CommandNotAwaitingApprovalException(makerCheckerId);
}
this.context.authenticatedUser().validateHasCheckerPermissionTo(commandSourceInput.getPermissionCode());
return commandSourceInput;
AppUser appUser = this.context.authenticatedUser();
String permissionCode = commandSource.getPermissionCode();
appUser.validateHasCheckerPermissionTo(permissionCode);
if (!configurationService.isSameMakerCheckerEnabled() && !appUser.isCheckerSuperUser()) {
AppUser maker = commandSource.getMaker();
if (maker == null) {
throw new UnsupportedCommandException(permissionCode, "Maker user is missing.");
}
if (Objects.equals(appUser.getId(), maker.getId())) {
throw new UnsupportedCommandException(permissionCode, "Can not be checked by the same user.");
}
}
return commandSource;
}
private void validateIsUpdateAllowed() {
@@ -35,14 +35,12 @@ import org.apache.fineract.batch.exception.ErrorInfo;
import org.apache.fineract.commands.domain.CommandProcessingResultType;
import org.apache.fineract.commands.domain.CommandSource;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
import org.apache.fineract.commands.exception.UnsupportedCommandException;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.commands.provider.CommandHandlerProvider;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder;
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
@@ -58,7 +56,6 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Slf4j
@@ -76,9 +73,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
private final ConfigurationDomainService configurationDomainService;
private final CommandHandlerProvider commandHandlerProvider;
private final IdempotencyKeyResolver idempotencyKeyResolver;
private final IdempotencyKeyGenerator idempotencyKeyGenerator;
private final CommandSourceService commandSourceService;
private final ErrorHandler errorHandler;
private final FineractRequestContextHolder fineractRequestContextHolder;
private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
@@ -92,73 +87,68 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
Long commandId = (Long) fineractRequestContextHolder.getAttribute(COMMAND_SOURCE_ID, null);
boolean isRetry = commandId != null;
boolean isEnclosingTransaction = BatchRequestContextHolder.isEnclosingTransaction();
CommandSource commandSource = null;
String idempotencyKey;
if (isRetry) {
commandSource = commandSourceService.getCommandSource(commandId);
idempotencyKey = commandSource.getIdempotencyKey();
} else if ((commandId = command.commandId()) != null) { // action on the command itself
commandSource = commandSourceService.getCommandSource(commandId);
idempotencyKey = commandSource.getIdempotencyKey();
} else {
idempotencyKey = idempotencyKeyResolver.resolve(wrapper);
}
exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey, isRetry);
boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent();
AppUser user = context.authenticatedUser(wrapper);
if (commandSource == null) {
AppUser user = context.authenticatedUser(wrapper);
if (sameTransaction) {
if (isEnclosingTransaction) {
commandSource = commandSourceService.getInitialCommandSource(wrapper, command, user, idempotencyKey);
} else {
commandSource = commandSourceService.saveInitialNewTransaction(wrapper, command, user, idempotencyKey);
storeCommandIdInContext(commandSource); // Store command id as a request attribute
commandId = commandSource.getId();
}
}
if (commandId != null) {
storeCommandIdInContext(commandSource); // Store command id as a request attribute
}
boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName());
if (isApprovedByChecker || (isMakerChecker && user.isCheckerSuperUser())) {
commandSource.markAsChecked(user);
}
setIdempotencyKeyStoreFlag(true);
final CommandProcessingResult result;
try {
result = findCommandHandler(wrapper).processCommand(command);
result = commandSourceService.processCommand(findCommandHandler(wrapper), command, commandSource, user, isApprovedByChecker,
isMakerChecker);
} catch (Throwable t) { // NOSONAR
RuntimeException mappable = ErrorHandler.getMappable(t);
ErrorInfo errorInfo = commandSourceService.generateErrorInfo(mappable);
commandSource.setResultStatusCode(errorInfo.getStatusCode());
Integer statusCode = errorInfo.getStatusCode();
commandSource.setResultStatusCode(statusCode);
commandSource.setResult(errorInfo.getMessage());
commandSource.setStatus(ERROR);
if (!sameTransaction) { // TODO: temporary solution
if (statusCode != SC_OK) {
commandSource.setStatus(ERROR);
}
if (!isEnclosingTransaction) { // TODO: temporary solution
commandSource = commandSourceService.saveResultNewTransaction(commandSource);
}
publishHookErrorEvent(wrapper, command, errorInfo); // TODO must be performed in a new transaction
// must not throw any exception; must persist in new transaction as the current transaction was already
// marked as rollback
publishHookErrorEvent(wrapper, command, errorInfo);
throw mappable;
}
commandSource.updateForAudit(result);
commandSource.setResultStatusCode(SC_OK);
commandSource.updateForAudit(result);
commandSource.setResult(toApiJsonSerializer.serializeResult(result));
commandSource.setStatus(PROCESSED);
boolean isRollback = !isApprovedByChecker && (result.isRollbackTransaction()
|| configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName()));
// TODO: this should be removed, can not override audit information (and maker-checker does not work)
if (!isRollback && result.hasChanges()) {
commandSource.setCommandJson(toApiJsonSerializer.serializeResult(result.getChanges()));
}
commandSource = commandSourceService.saveResultSameTransaction(commandSource);
if (sameTransaction) {
storeCommandIdInContext(commandSource); // Store command id as a request attribute
}
if (isRollback) {
/*
* JournalEntry will generate a new transactionId every time. Updating the transactionId with old
* transactionId, because as there are no entries are created with new transactionId, will throw an error
* when checker approves the transaction
*/
commandSource.setTransactionId(command.getTransactionId());
// TODO: this should be removed together with lines 147-149
commandSource.setCommandJson(command.json()); // Set back CommandSource json data
throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(commandSource);
}
storeCommandIdInContext(commandSource); // Store command id as a request attribute
result.setRollbackTransaction(null);
publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, result); // TODO must be performed in a
@@ -201,24 +191,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
fineractRequestContextHolder.setAttribute(IDEMPOTENCY_KEY_STORE_FLAG, flag);
}
@Transactional
@Override
public CommandProcessingResult logCommand(CommandSource commandSource) {
commandSource.markAsAwaitingApproval();
if (commandSource.getIdempotencyKey() == null) {
commandSource.setIdempotencyKey(idempotencyKeyGenerator.create());
}
commandSource = commandSourceService.saveResultSameTransaction(commandSource);
return new CommandProcessingResultBuilder().withCommandId(commandSource.getId()).withEntityId(commandSource.getResourceId())
.build();
}
@SuppressWarnings("unused")
public CommandProcessingResult fallbackExecuteCommand(Exception e) {
if (e instanceof RollbackTransactionAsCommandIsNotApprovedByCheckerException ex) {
return logCommand(ex.getCommandSourceResult());
}
throw ErrorHandler.getMappable(e);
}
@@ -281,10 +255,10 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
}
@Override
public boolean validateCommand(final CommandWrapper commandWrapper, final AppUser user) {
boolean rollbackTransaction = configurationDomainService.isMakerCheckerEnabledForTask(commandWrapper.taskPermissionName());
public boolean validateRollbackCommand(final CommandWrapper commandWrapper, final AppUser user) {
user.validateHasPermissionTo(commandWrapper.getTaskPermissionName());
return rollbackTransaction;
boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(commandWrapper.taskPermissionName());
return isMakerChecker && !user.isCheckerSuperUser();
}
private void publishHookEvent(final String entityName, final String actionName, JsonCommand command, final Object result) {
@@ -25,6 +25,8 @@ public interface ConfigurationDomainService {
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
boolean isSameMakerCheckerEnabled();
boolean isAmazonS3Enabled();
boolean isRescheduleFutureRepaymentsEnabled();
@@ -27,15 +27,16 @@ public final class BatchRequestContextHolder {
private BatchRequestContextHolder() {}
private static final ThreadLocal<Map<String, Object>> batchAttributes = new NamedThreadLocal<>("BatchAttributesForProcessing");
private static final ThreadLocal<Map<String, Object>> batchAttributes = new NamedThreadLocal<>("batchAttributes");
private static final ThreadLocal<Optional<TransactionStatus>> enclosingTransaction = new NamedThreadLocal<>("EnclosingTransaction") {
private static final ThreadLocal<Optional<TransactionStatus>> batchTransaction = new NamedThreadLocal<>("batchTransaction") {
@Override
protected Optional<TransactionStatus> initialValue() {
return Optional.empty();
}
};
private static final ThreadLocal<Boolean> isEnclosingTransaction = new NamedThreadLocal<>("isEnclosingTransaction");
/**
* True if the batch attributes are set
@@ -47,12 +48,12 @@ public final class BatchRequestContextHolder {
}
/**
* True if the batch attributes are set and the enclosing transaction is set to true
* Returns the batch attributes for the current thread.
*
* @return
* @return the batch attributes for the current thread, cna be null
*/
public static Optional<TransactionStatus> getEnclosingTransaction() {
return enclosingTransaction.get();
public static Map<String, Object> getRequestAttributes() {
return batchAttributes.get();
}
/**
@@ -65,15 +66,6 @@ public final class BatchRequestContextHolder {
batchAttributes.set(requestAttributes);
}
/**
* Returns the batch attributes for the current thread.
*
* @return the batch attributes for the current thread, cna be null
*/
public static Map<String, Object> getRequestAttributes() {
return batchAttributes.get();
}
/**
* Reset the batch attributes for the current thread.
*/
@@ -82,11 +74,66 @@ public final class BatchRequestContextHolder {
}
/**
* Set the enclosing transaction flag for the current thread.
* True if the batch attributes are set and the enclosing transaction is set to true
*
* @return
*/
public static boolean isEnclosingTransaction() {
return Boolean.TRUE.equals(isEnclosingTransaction.get());
}
/**
* Set the isEnclosingTransaction flag for the current thread.
*
* @param isEnclosingTransaction
*/
public static void setIsEnclosingTransaction(boolean isEnclosingTransaction) {
BatchRequestContextHolder.isEnclosingTransaction.set(isEnclosingTransaction);
}
public static void resetIsEnclosingTransaction() {
isEnclosingTransaction.remove();
}
/**
* Return the transaction
*
* @return
*/
public static Optional<TransactionStatus> getTransaction() {
return batchTransaction.get();
}
/**
* Return the enclosing transaction
*
* @return
*/
public static Optional<TransactionStatus> getEnclosingTransaction() {
return isEnclosingTransaction() ? getTransaction() : Optional.empty();
}
/**
* Set the transaction for the current thread.
*
* @param enclosingTransaction
*/
public static void setEnclosingTransaction(Optional<TransactionStatus> enclosingTransaction) {
BatchRequestContextHolder.enclosingTransaction.set(enclosingTransaction);
public static void setTransaction(TransactionStatus enclosingTransaction) {
batchTransaction.set(Optional.ofNullable(enclosingTransaction));
}
/**
* Set the enclosing transaction for the current thread.
*
* @param enclosingTransaction
*/
public static void setEnclosingTransaction(TransactionStatus enclosingTransaction) {
if (isEnclosingTransaction()) {
setTransaction(enclosingTransaction);
}
}
public static void resetTransaction() {
batchTransaction.set(Optional.empty());
}
}
@@ -0,0 +1,56 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.exceptionmapper;
import com.google.gson.Gson;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* An {@link ExceptionMapper} to map {@link PlatformApiDataValidationException} thrown by platform into a HTTP API
* friendly format.
*
* The {@link PlatformApiDataValidationException} is typically thrown in data validation of the parameters passed in
* with an api request.
*/
@Provider
@Component
@Scope("singleton")
@Slf4j
public class RollbackTransactionNotApprovedExceptionMapper
implements FineractExceptionMapper, ExceptionMapper<RollbackTransactionNotApprovedException> {
@Override
public Response toResponse(final RollbackTransactionNotApprovedException exception) {
log.warn("Exception: {}", exception.getClass().getName());
return Response.ok().entity(new Gson().toJson(exception.getResult())).type(MediaType.APPLICATION_JSON).build();
}
@Override
public int errorCode() {
return 2000;
}
}
@@ -47,8 +47,12 @@ public class UnsupportedCommandExceptionMapper implements ExceptionMapper<Unsupp
final List<ApiParameterError> errors = new ArrayList<>();
final StringBuilder validationErrorCode = new StringBuilder("error.msg.command.unsupported");
String message = exception.getMessage();
final StringBuilder defaultEnglishMessage = new StringBuilder("The command ").append(exception.getUnsupportedCommandName())
.append(" is not supported.");
if (message != null) {
defaultEnglishMessage.append(" ").append(message);
}
log.warn("Exception: {}, Message: {}", exception.getClass().getName(), defaultEnglishMessage);
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
exception.getUnsupportedCommandName(), exception.getUnsupportedCommandName());
@@ -25,7 +25,6 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.domain.CommandSourceRepository;
import org.apache.fineract.commands.service.CommandSourceService;
import org.apache.fineract.commands.service.SynchronousCommandProcessingService;
import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder;
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
import org.springframework.stereotype.Component;
@@ -39,11 +38,9 @@ public class IdempotencyStoreHelper {
public void storeCommandResult(Integer response, String body, Long commandId) {
commandSourceRepository.findById(commandId).ifPresent(commandSource -> {
boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent();
commandSource.setResultStatusCode(response);
commandSource.setResult(body);
commandSource = sameTransaction ? commandSourceService.saveResultSameTransaction(commandSource)
: commandSourceService.saveResultNewTransaction(commandSource);
commandSourceService.saveResultSameTransaction(commandSource);
});
}
@@ -657,6 +657,10 @@ public class AppUser extends AbstractPersistableCustom implements PlatformUser {
}
}
public boolean isCheckerSuperUser() {
return hasPermissionTo("CHECKER_SUPER_USER");
}
public void validateHasDatatableReadPermission(final String datatable) {
if (hasNotPermissionForDatatable(datatable, "READ")) {
throw new NoAuthorizationException("Not authorised to read datatable: " + datatable);
@@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.exceptionmapper.IdempotentCommandExceptionMapper;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -48,6 +49,11 @@ public class IdempotencyCommandProcessFailedExceptionTest {
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void testInconsistentStatus() {
IdempotentCommandExceptionMapper mapper = new IdempotentCommandExceptionMapper();
@@ -54,6 +54,7 @@ import org.apache.fineract.investor.service.AccountingService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -90,6 +91,11 @@ public class LoanAccountOwnerTransferBusinessStepTest {
externalAssetOwnerTransferLoanMappingRepository, accountingService, businessEventNotifierService);
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void givenLoanNoTransfer() {
// given
@@ -38,6 +38,7 @@ import org.apache.fineract.infrastructure.bulkimport.importhandler.helper.EnumOp
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsActivation;
@@ -147,7 +148,9 @@ public class SavingsImportHandler implements ImportHandler {
Long interestPostingPeriodTypeId = null;
EnumOptionData interestPostingPeriodTypeEnum = null;
if (interestPostingPeriodType != null) {
if (interestPostingPeriodType.equalsIgnoreCase(MONTHLY)) {
if (interestPostingPeriodType.equalsIgnoreCase(DAILY)) {
interestPostingPeriodTypeId = SavingsPostingInterestPeriodType.DAILY.getValue().longValue();
} else if (interestPostingPeriodType.equalsIgnoreCase(MONTHLY)) {
interestPostingPeriodTypeId = 4L;
} else if (interestPostingPeriodType.equalsIgnoreCase(QUARTERLY)) {
interestPostingPeriodTypeId = 5L;
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.infrastructure.configuration.domain;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -74,6 +75,11 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
return false;
}
@Override
public boolean isSameMakerCheckerEnabled() {
return getGlobalConfigurationPropertyData("enable-same-maker-checker").isEnabled();
}
@Override
public boolean isAmazonS3Enabled() {
return getGlobalConfigurationPropertyData("amazon-S3").isEnabled();
@@ -379,18 +385,14 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
return property.getValue();
}
@NotNull
private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) {
return globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName).toData();
}
@Override
public boolean isSubRatesEnabled() {
GlobalConfigurationPropertyData configuration = getGlobalConfigurationPropertyData("sub-rates");
if (configuration == null) {
return false;
} else {
return configuration.isEnabled();
}
return getGlobalConfigurationPropertyData("sub-rates").isEnabled();
}
@Override
@@ -294,7 +294,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
validateParentGroupRulesBeforeClientActivation(newClient);
runEntityDatatableCheck(newClient.getId(), newClient.getLegalForm());
final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateClient(null).build();
rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser);
rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser);
}
this.clientRepository.saveAndFlush(newClient);
@@ -172,16 +172,16 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
if (newGroup.isCenter()) {
final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateCenter(null).build();
rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser);
rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser);
} else {
final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateGroup(null).build();
rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser);
rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser);
}
}
if (!newGroup.isCenter() && newGroup.hasActiveClients()) {
final CommandWrapper commandWrapper = new CommandWrapperBuilder().associateClientsToGroup(newGroup.getId()).build();
rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser);
rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser);
}
// pre-save to generate id for use in group hierarchy
@@ -656,7 +656,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
public CommandProcessingResult createActiveApplication(final SavingsAccountDataDTO savingsAccountDataDTO) {
final CommandWrapper commandWrapper = new CommandWrapperBuilder().savingsAccountActivation(null).build();
boolean rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, savingsAccountDataDTO.getAppliedBy());
boolean rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper,
savingsAccountDataDTO.getAppliedBy());
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsAccountDataDTO.getClient(),
savingsAccountDataDTO.getGroup(), savingsAccountDataDTO.getSavingsProduct(), savingsAccountDataDTO.getApplicationDate(),
@@ -150,4 +150,5 @@
<include file="parts/0128_savings_audit.xml" relativeToChangelogFile="true" />
<include file="parts/0129_transaction_summary_with_asset_owner_report_overpaid_amount.xml" relativeToChangelogFile="true" />
<include file="parts/0130_add_create_delinquency_action_permission.xml" relativeToChangelogFile="true" />
<include file="parts/0131_add_configuration_maker_checker.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<insert tableName="c_configuration">
<column name="name" value="enable-same-maker-checker"/>
<column name="value"/>
<column name="date_value"/>
<column name="string_value"/>
<column name="enabled" valueBoolean="false"/>
<column name="is_trap_door" valueBoolean="false"/>
<column name="description"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -66,7 +66,11 @@ public class LoanItemListenerStepDefinitions implements En {
});
When("LoanItemListener.onReadError method executed", () -> {
loanItemListener.onReadError(exception);
try {
loanItemListener.onReadError(exception);
} finally {
ThreadLocalContextUtil.reset();
}
});
Then("LoanItemListener.onReadError result should match", () -> {
@@ -87,7 +91,11 @@ public class LoanItemListenerStepDefinitions implements En {
});
When("LoanItemListener.onProcessError method executed", () -> {
loanItemListener.onProcessError(loan, exception);
try {
loanItemListener.onProcessError(loan, exception);
} finally {
ThreadLocalContextUtil.reset();
}
});
Then("LoanItemListener.onProcessError result should match", () -> {
@@ -108,7 +116,11 @@ public class LoanItemListenerStepDefinitions implements En {
});
When("LoanItemListener.onWriteError method executed", () -> {
loanItemListener.onWriteError(exception, new Chunk<>(List.of(loan)));
try {
loanItemListener.onWriteError(exception, new Chunk<>(List.of(loan)));
} finally {
ThreadLocalContextUtil.reset();
}
});
Then("LoanItemListener.onWriteError result should match", () -> {
@@ -42,6 +42,7 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -66,6 +67,11 @@ public class AddPeriodicAccrualEntriesBusinessStepTest {
underTest = new AddPeriodicAccrualEntriesBusinessStep(loanAccrualPlatformService);
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void givenLoanWithAccrual() throws MultiException {
// given
@@ -122,7 +122,11 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
});
When("ApplyLoanLockTasklet.execute method executed", () -> {
resultItem = applyLoanLockTasklet.execute(stepContribution, null);
try {
resultItem = applyLoanLockTasklet.execute(stepContribution, null);
} finally {
ThreadLocalContextUtil.reset();
}
});
Then("ApplyLoanLockTasklet.execute result should match", () -> {
@@ -46,6 +46,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -72,6 +73,11 @@ public class CheckLoanRepaymentDueBusinessStepTest {
underTest = new CheckLoanRepaymentDueBusinessStep(configurationDomainService, businessEventNotifierService);
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void givenLoanWithInstallmentDueAfterConfiguredDaysWhenStepExecutionThenBusinessEventIsRaised() {
ArgumentCaptor<LoanRepaymentDueBusinessEvent> loanRepaymentDueEvent = ArgumentCaptor.forClass(LoanRepaymentDueBusinessEvent.class);
@@ -43,6 +43,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -69,6 +70,11 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
underTest = new CheckLoanRepaymentOverdueBusinessStep(configurationDomainService, businessEventNotifierService);
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void givenLoanWithInstallmentOverdueAfterConfiguredDaysWhenStepExecutionThenBusinessEventIsRaised() {
ArgumentCaptor<LoanRepaymentOverdueBusinessEvent> loanRepaymentDueBusinessEventArgumentCaptor = ArgumentCaptor
@@ -169,5 +175,4 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
assertEquals(repaymentInstallment, loanPayloadForEvent);
assertEquals(processedLoan, loanForProcessing);
}
}
@@ -40,6 +40,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -73,6 +74,11 @@ class LoanItemReaderTest {
@Mock
private Loan loan;
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void testLoanItemReaderSimple() throws Exception {
// given
@@ -163,5 +169,4 @@ class LoanItemReaderTest {
Mockito.verifyNoMoreInteractions(loanRepository);
}
}
@@ -47,6 +47,7 @@ import org.apache.fineract.portfolio.delinquency.helper.DelinquencyEffectivePaus
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -100,6 +101,11 @@ public class SetLoanDelinquencyTagsBusinessStepTest {
delinquencyReadPlatformService, businessEventNotifierService);
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
/**
* Tests {@link SetLoanDelinquencyTagsBusinessStep#execute(Loan)} success scenario.
*
@@ -85,7 +85,7 @@ public class COBBulkEventConfigurationTest {
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
ThreadLocalContextUtil.reset();
}
@Test
@@ -42,6 +42,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -73,6 +74,11 @@ class InlineLoanCOBExecutorServiceImplTest {
@Mock
private FineractProperties.FineractBodyItemSizeLimitProperties fineractBodyItemSizeLimitProperties;
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
void shouldExceptionThrownIfLoanIsAlreadyLocked() {
JsonCommand command = mock(JsonCommand.class);
@@ -37,6 +37,7 @@ import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -71,6 +72,11 @@ public class PurgeProcessedCommandsTaskletTest {
underTest = new PurgeProcessedCommandsTasklet(repository, configurationDomainService);
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void givenEventsForPurgeWhenTaskExecutionThenEventsPurgeForDaysCriteria() {
// given
@@ -97,5 +103,4 @@ public class PurgeProcessedCommandsTaskletTest {
// then
assertEquals(RepeatStatus.FINISHED, resultStatus);
}
}
@@ -25,13 +25,10 @@ import static org.mockito.ArgumentMatchers.any;
import io.cucumber.java8.En;
import io.github.resilience4j.retry.RetryRegistry;
import io.github.resilience4j.retry.event.RetryEvent;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.fineract.commands.domain.CommandSource;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
import org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
@@ -69,7 +66,7 @@ public class CommandServiceStepDefinitions implements En {
ReflectionTestUtils.setField(processAndLogCommandService, "fineractRequestContextHolder", contextHolder);
Mockito.when(contextHolder.getAttribute(any(), any())).thenThrow(new CannotAcquireLockException("BLOW IT UP!!!"))
.thenThrow(new ObjectOptimisticLockingFailureException("Dummy", new RuntimeException("BLOW IT UP!!!")))
.thenThrow(new RollbackTransactionAsCommandIsNotApprovedByCheckerException(new DummyCommandSource()));
.thenThrow(new RollbackTransactionNotApprovedException(1L, null));
this.retryRegistry.retry("executeCommand").getEventPublisher().onRetry(event -> {
log.warn("... retry event: {}", event);
@@ -147,13 +144,4 @@ public class CommandServiceStepDefinitions implements En {
return null;
}
}
@Entity
@Table(name = "m_portfolio_command_source")
public static class DummyCommandSource extends CommandSource {
public DummyCommandSource() {
setId(1L);
}
}
}
@@ -89,24 +89,12 @@ public class CommandSourceServiceTest {
@Test
public void testCreateFromExisting() {
CommandWrapper wrapper = CommandWrapper.wrap("act", "ent", 1L, 1L);
long commandId = 1L;
JsonCommand jsonCommand = JsonCommand.fromExistingCommand(commandId, "", null, null, null, 1L, null, null, null, null, null, null,
null, null, null, null, null);
CommandSource commandMock = Mockito.mock(CommandSource.class);
Mockito.when(commandSourceRepository.saveAndFlush(commandMock)).thenReturn(commandMock);
Mockito.when(commandSourceRepository.findById(commandId)).thenReturn(Optional.of(commandMock));
AppUser appUser = Mockito.mock(AppUser.class);
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "t1", "n1", ZoneId.systemDefault().toString(), null));
CommandSource actual = underTest.saveInitialNewTransaction(wrapper, jsonCommand, appUser, "idk");
ArgumentCaptor<CommandSource> commandSourceArgumentCaptor = ArgumentCaptor.forClass(CommandSource.class);
Mockito.verify(commandSourceRepository).saveAndFlush(commandSourceArgumentCaptor.capture());
CommandSource captured = commandSourceArgumentCaptor.getValue();
Assertions.assertEquals(actual, captured);
CommandSource actual = underTest.getCommandSource(commandId);
Assertions.assertEquals(commandMock, actual);
}
@Test
@@ -67,8 +67,6 @@ public class SynchronousCommandProcessingServiceTest {
@Mock
private IdempotencyKeyResolver idempotencyKeyResolver;
@Mock
private IdempotencyKeyGenerator idempotencyKeyGenerator;
@Mock
private CommandSourceService commandSourceService;
@Spy
@@ -93,19 +91,21 @@ public class SynchronousCommandProcessingServiceTest {
when(commandWrapper.isNoteResource()).thenReturn(false);
when(commandWrapper.isSurveyResource()).thenReturn(false);
when(commandWrapper.isLoanDisburseDetailResource()).thenReturn(false);
JsonCommand jsonCommand = Mockito.mock(JsonCommand.class);
NewCommandSourceHandler newCommandSourceHandler = Mockito.mock(NewCommandSourceHandler.class);
long commandId = 1L;
JsonCommand jsonCommand = Mockito.mock(JsonCommand.class);
when(jsonCommand.commandId()).thenReturn(commandId);
NewCommandSourceHandler commandHandler = Mockito.mock(NewCommandSourceHandler.class);
CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class);
when(commandProcessingResult.isRollbackTransaction()).thenReturn(false);
when(newCommandSourceHandler.processCommand(jsonCommand)).thenReturn(commandProcessingResult);
when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(newCommandSourceHandler);
when(commandHandler.processCommand(jsonCommand)).thenReturn(commandProcessingResult);
when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(commandHandler);
when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false);
String idk = "idk";
when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk);
CommandSource commandSource = Mockito.mock(CommandSource.class);
long commandId = 1L;
when(commandSource.getId()).thenReturn(commandId);
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null);
when(commandSourceService.getCommandSource(commandId)).thenReturn(commandSource);
@@ -116,9 +116,12 @@ public class SynchronousCommandProcessingServiceTest {
when(commandSource.getStatus()).thenReturn(CommandProcessingResultType.PROCESSED.getValue());
when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser);
when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false, false))
.thenReturn(commandProcessingResult);
CommandProcessingResult actualCommandProcessingResult = underTest.executeCommand(commandWrapper, jsonCommand, false);
verify(commandSourceService).saveInitialNewTransaction(commandWrapper, jsonCommand, appUser, idk);
verify(commandSourceService).getCommandSource(commandId);
assertEquals(CommandProcessingResultType.PROCESSED.getValue(), commandSource.getStatus());
verify(commandSourceService).saveResultSameTransaction(commandSource);
@@ -133,19 +136,21 @@ public class SynchronousCommandProcessingServiceTest {
when(commandWrapper.isSurveyResource()).thenReturn(false);
when(commandWrapper.isLoanDisburseDetailResource()).thenReturn(false);
JsonCommand jsonCommand = Mockito.mock(JsonCommand.class);
Long commandId = jsonCommand.commandId();
NewCommandSourceHandler newCommandSourceHandler = Mockito.mock(NewCommandSourceHandler.class);
NewCommandSourceHandler commandHandler = Mockito.mock(NewCommandSourceHandler.class);
CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class);
CommandSource commandSource = Mockito.mock(CommandSource.class);
when(commandProcessingResult.isRollbackTransaction()).thenReturn(false);
RuntimeException runtimeException = new RuntimeException("foo");
when(newCommandSourceHandler.processCommand(jsonCommand)).thenThrow(runtimeException);
when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(newCommandSourceHandler);
when(commandHandler.processCommand(jsonCommand)).thenThrow(runtimeException);
when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(commandHandler);
when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false);
String idk = "idk";
when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk);
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null);
when(commandSourceService.getCommandSource(commandId)).thenReturn(commandSource);
AppUser appUser = Mockito.mock(AppUser.class);
when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser);
@@ -155,11 +160,14 @@ public class SynchronousCommandProcessingServiceTest {
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(initialCommandSource);
when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false, false))
.thenThrow(runtimeException);
Assertions.assertThrows(RuntimeException.class, () -> {
underTest.executeCommand(commandWrapper, jsonCommand, false);
});
verify(commandSourceService).saveInitialNewTransaction(commandWrapper, jsonCommand, appUser, idk);
verify(commandSourceService).getCommandSource(commandId);
verify(commandSourceService).generateErrorInfo(runtimeException);
}
}
@@ -42,6 +42,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -79,6 +80,11 @@ public class BusinessDateWritePlatformServiceTest {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void businessDateIsNotEnabled() {
JsonCommand command = JsonCommand.from("");
@@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDa
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -48,6 +49,11 @@ public class CustomAuditingHandlerTest {
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.of("Asia/Kolkata")))));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void markCreated() {
MappingContext mappingContext = Mockito.mock(MappingContext.class);
@@ -111,5 +117,4 @@ public class CustomAuditingHandlerTest {
assertEquals(now.getHour(), targetObject.getCreatedDate().get().getHour());
assertEquals(now.getMinute(), targetObject.getCreatedDate().get().getMinute());
}
}
@@ -28,6 +28,7 @@ import java.util.Optional;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -38,6 +39,11 @@ public class CustomDateTimeProviderTest {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void instanceDateProvider() {
Optional<TemporalAccessor> dateTimeProvider = CustomDateTimeProvider.INSTANCE.getNow();
@@ -65,5 +71,4 @@ public class CustomDateTimeProviderTest {
assertEquals(now.getHour(), ((OffsetDateTime) dateTimeProvider.get()).getHour());
assertEquals(now.getMinute(), ((OffsetDateTime) dateTimeProvider.get()).getMinute());
}
}
@@ -32,6 +32,7 @@ import java.util.Map;
import java.util.TimeZone;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -43,6 +44,11 @@ public class DateUtilsTest {
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 12))));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void getSystemZoneIdTest() {
TimeZone.setDefault(TimeZone.getTimeZone("GMT+02:00"));
@@ -21,12 +21,26 @@ package org.apache.fineract.infrastructure.dataqueries.service;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.dataqueries.service.export.DatatableExportUtil;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class DatatableExportUtilTest {
@BeforeEach
public void setUp() {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void emptyFolderTest() {
Assert.assertEquals("", DatatableExportUtil.normalizeFolderName(""));
@@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.event.external.repository.ExternalEventRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -69,6 +70,11 @@ public class PurgeExternalEventsTaskletTest {
underTest = new PurgeExternalEventsTasklet(repository, configurationDomainService);
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void givenEventsForPurgeWhenTaskExecutionThenEventsPurgeForDaysCriteria() {
// given
@@ -95,5 +101,4 @@ public class PurgeExternalEventsTaskletTest {
// then
assertEquals(RepeatStatus.FINISHED, resultStatus);
}
}
@@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.event.external.repository.ExternalEven
import org.apache.fineract.infrastructure.event.external.repository.domain.ExternalEventView;
import org.apache.fineract.infrastructure.event.external.service.message.MessageFactory;
import org.apache.fineract.infrastructure.event.external.service.support.ByteBufferConverter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -97,6 +98,11 @@ class SendAsynchronousEventsTaskletTest {
configurationDomainService);
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
private void configureExternalEventsProducerReadBatchSizeProperty() {
FineractProperties.FineractEventsProperties eventsProperties = new FineractProperties.FineractEventsProperties();
FineractProperties.FineractExternalEventsProperties externalProperties = new FineractProperties.FineractExternalEventsProperties();
@@ -26,10 +26,16 @@ import java.util.Map;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
class ExternalEventTest {
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void testConstructorWorks() {
// given
@@ -56,6 +62,5 @@ class ExternalEventTest {
assertThat(result.getBusinessDate()).isEqualTo(currentBusinessDate);
assertThat(result.getCreatedAt()).isNotNull();
assertThat(result.getSentAt()).isNull();
}
}
@@ -57,6 +57,7 @@ import org.apache.fineract.infrastructure.event.external.service.support.ByteBuf
import org.apache.fineract.investor.enricher.LoanAccountDataV1Enricher;
import org.apache.fineract.investor.enricher.LoanTransactionAdjustmentDataV1Enricher;
import org.apache.fineract.investor.enricher.LoanTransactionDataV1Enricher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -110,6 +111,11 @@ class ExternalEventServiceTest {
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void testPostEventShouldFailWhenNullEventIsGiven() {
// given
@@ -70,6 +70,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -105,6 +106,11 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void testLoanDelinquencyRangeEventPayloadSerialization() throws IOException {
// given
@@ -340,5 +346,4 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
ReflectionTestUtils.setField(loanCharge, "id", 1L);
return loanCharge;
}
}
@@ -47,6 +47,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -78,6 +79,11 @@ public class LoanAdjustTransactionBusinessEventSerializerTest {
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
void loanTransactionReversedOnDateSerializationTest() {
Loan loanForProcessing = Mockito.mock(Loan.class);
@@ -107,5 +113,4 @@ public class LoanAdjustTransactionBusinessEventSerializerTest {
.toAvroDTO(businessEvent);
assertEquals(reversedLocalDate, loanTransactionAdjustmentDataV1.getTransactionToAdjust().getReversedOnDate());
}
}
@@ -90,6 +90,7 @@ public class LoanRepaymentBusinessEventSerializerTest {
@AfterEach
public void reset() {
ThreadLocalContextUtil.reset();
moneyHelper.close();
}
@@ -181,5 +182,4 @@ public class LoanRepaymentBusinessEventSerializerTest {
AvroRuntimeException exceptionThrown = assertThrows(AvroRuntimeException.class, () -> serializer.toAvroDTO(event));
assertTrue(exceptionThrown.getMessage().contains("does not accept null values"));
}
}
@@ -56,6 +56,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -99,6 +100,11 @@ class LoanCOBApiFilterTest {
testObj = new LoanCOBApiFilter(helper);
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
void shouldLoanAndExternalMatchToo() {
String externalId = UUID.randomUUID().toString();
@@ -45,6 +45,7 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.investor.enricher.LoanAccountDataV1Enricher;
import org.apache.fineract.investor.enricher.LoanChargeDataV1Enricher;
import org.apache.fineract.investor.enricher.LoanTransactionDataV1Enricher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -66,6 +67,11 @@ public class EnricherTest {
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void testLoanAccountDataEnricher() {
LoanAccountDataV1Enricher loanAccountDataV1Enricher = mock(LoanAccountDataV1Enricher.class);
@@ -178,5 +184,4 @@ public class EnricherTest {
assertEquals(PURCHASE_PRICE_RATIO, data.getPurchasePriceRatio());
}
}
@@ -76,6 +76,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -130,6 +131,11 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void givenLoanAccountWithDelinquencyBucketWhenRangeChangeThenEventIsRaised() {
ArgumentCaptor<LoanDelinquencyRangeChangeBusinessEvent> loanDeliquencyRangeChangeEvent = ArgumentCaptor
@@ -614,5 +620,4 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
Loan loanPayloadForEvent = loanDelinquencyRangeChangeEvent.getValue().get();
assertEquals(loanForProcessing, loanPayloadForEvent);
}
}
@@ -105,6 +105,7 @@ public class LoanDelinquencyDomainServiceTest {
@AfterEach
public void deregister() {
ThreadLocalContextUtil.reset();
moneyHelperStatic.close();
}
@@ -27,16 +27,23 @@ import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
@@ -46,11 +53,21 @@ import org.springframework.test.util.ReflectionTestUtils;
*/
public class LoanTest {
private final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault());
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
/**
* Tests {@link Loan#getCharges()} with charges.
*/
@Test
public void testGetChargesWithCharges() {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
Loan loan = new Loan();
ReflectionTestUtils.setField(loan, "charges", Collections.singleton(buildLoanCharge()));
@@ -48,6 +48,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -83,6 +84,11 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate)));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test
public void chargePaymentTransactionTestWithExactAmount() {
final BigDecimal chargeAmount = BigDecimal.valueOf(100);
@@ -44,6 +44,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -104,6 +105,11 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate)));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
// IN ADVANCE
@Test
public void inAdvancePaymentOfPrincipal() {
@@ -44,6 +44,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -105,6 +106,11 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate)));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
// IN ADVANCE
@Test
public void inAdvancePaymentOfPrincipal() {
@@ -69,6 +69,7 @@ public class LoanCalculateRepaymentPastDueServiceTest {
@AfterEach
public void reset() {
ThreadLocalContextUtil.reset();
moneyHelper.close();
}
@@ -2153,12 +2153,7 @@ public class BatchApiTest {
LOG.info("Batch Response : {}", new Gson().toJson(responseOfQueryAndUpdateDatatableBatch));
final BatchResponse queryResponse = responseOfQueryAndUpdateDatatableBatch.get(0);
Assertions.assertEquals(1L, queryResponse.getRequestId());
Assertions.assertEquals(HttpStatus.SC_OK, queryResponse.getStatusCode(), "Verify Status Code 200 for query datatable entry");
final BatchResponse updateResponse = responseOfQueryAndUpdateDatatableBatch.get(1);
final BatchResponse updateResponse = responseOfQueryAndUpdateDatatableBatch.get(0);
Assertions.assertEquals(2L, updateResponse.getRequestId());
Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, updateResponse.getStatusCode(),
@@ -21,6 +21,7 @@ package org.apache.fineract.integrationtests;
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.PAUSE;
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -109,8 +110,10 @@ public class DelinquencyBucketsIntegrationTest {
// then
assertNotNull(delinquencyRangeResponse01);
assertNotNull(ranges);
assertEquals(1, ranges.get(0).getMinimumAgeDays(), "Expected Min Age Days to 1");
assertEquals(3, ranges.get(0).getMaximumAgeDays(), "Expected Max Age Days to 3");
assertFalse(ranges.isEmpty());
GetDelinquencyRangesResponse range = ranges.get(ranges.size() - 1);
assertEquals(1, range.getMinimumAgeDays(), "Expected Min Age Days to 1");
assertEquals(3, range.getMaximumAgeDays(), "Expected Max Age Days to 3");
}
@Test
@@ -19,22 +19,25 @@
package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.fineract.cob.data.BusinessStep;
import org.apache.fineract.cob.data.JobBusinessStepConfigData;
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
import org.apache.fineract.integrationtests.common.IdempotencyHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -70,9 +73,9 @@ public class IdempotencyTest {
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Response responseSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Assertions.assertEquals(response.getBody().asString(), responseSecond.getBody().asString());
Assertions.assertNull(response.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertNotNull(responseSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
assertEquals(response.getBody().asString(), responseSecond.getBody().asString());
assertNull(response.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
assertNotNull(responseSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
idempotencyKeyHeader = UUID.randomUUID().toString();
@@ -89,9 +92,9 @@ public class IdempotencyTest {
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Response updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME);
applyChargeStep = newStepConfig.getBusinessSteps().stream()
@@ -109,9 +112,9 @@ public class IdempotencyTest {
updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME);
applyChargeStep = newStepConfig.getBusinessSteps().stream()
@@ -126,14 +129,14 @@ public class IdempotencyTest {
updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(originalStepConfig.getBusinessSteps()), idempotencyKeyHeader);
Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
}
@Test
public void shoudTheSecondRequestWithSameIdempotencyKeyWillFailureToo() {
public void shouldTheSecondRequestWithSameIdempotencyKeyWillFailureToo() {
ResponseSpecification responseSpecForError = new ResponseSpecBuilder().expectStatusCode(400).build();
List<BusinessStep> requestBody = new ArrayList<>();
String idempotencyKey = UUID.randomUUID().toString();
@@ -141,13 +144,14 @@ public class IdempotencyTest {
Response response1 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKey);
Assertions.assertNull(response1.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
String originalBody = response1.getBody().asString();
assertNull(response1.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
ResponseBody body1 = response1.getBody();
assertNotNull(body1);
Response response2 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKey);
Assertions.assertNotNull(response2.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertEquals(originalBody, response2.getBody().asString());
assertNotNull(response2.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
assertEquals((Map) body1.jsonPath().get(""), response2.getBody().jsonPath().get(""));
}
private BusinessStep getBusinessSteps(Long order, String stepName) {
@@ -181,8 +181,8 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN)
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en"));
final float feePortion = 50.0f;
final float penaltyPortion = 100.0f;
final double feePortion = 50.0d;
final double penaltyPortion = 100.0d;
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateWithAccountTransferJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
@@ -209,8 +209,8 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
assertEquals(feePortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding());
assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue());
assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding());
assertEquals(400.0f, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod());
assertEquals(400.0f, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod());
assertEquals(400.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod());
assertEquals(400.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod());
assertEquals(LocalDate.of(2023, 1, 16), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
scheduleJobHelper.executeAndAwaitJob(jobName);
@@ -218,11 +218,11 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
assertEquals(5, loanDetails.getRepaymentSchedule().getPeriods().size());
assertEquals(feePortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesDue());
assertEquals(0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding());
assertEquals(0.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding());
assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue());
assertEquals(0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding());
assertEquals(400, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod());
assertEquals(250, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod());
assertEquals(0.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding());
assertEquals(400.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod());
assertEquals(250.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod());
assertEquals(LocalDate.of(2023, 1, 16), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
@@ -18,17 +18,31 @@
*/
package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList;
import org.apache.fineract.client.models.GetMakerCheckerResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.fineract.client.models.GlobalConfigurationPropertyData;
import org.apache.fineract.client.models.PutPermissionsRequest;
import org.apache.fineract.integrationtests.common.AuditHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.commands.MakercheckersHelper;
import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
import org.apache.fineract.integrationtests.useradministration.roles.RolesHelper;
import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -38,6 +52,12 @@ public class MakercheckerTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private MakercheckersHelper makercheckersHelper;
private RolesHelper rolesHelper;
private AuditHelper auditHelper;
private SavingsProductHelper savingsProductHelper;
private SavingsAccountHelper savingsAccountHelper;
private static final String START_DATE_STRING = "03 June 2023";
private static final String TRANSACTION_DATE_STRING = "05 June 2023";
@BeforeEach
public void setup() {
@@ -46,14 +66,151 @@ public class MakercheckerTest {
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
this.makercheckersHelper = new MakercheckersHelper(this.requestSpec, this.responseSpec);
this.rolesHelper = new RolesHelper();
this.auditHelper = new AuditHelper(requestSpec, responseSpec);
this.savingsProductHelper = new SavingsProductHelper();
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
}
@Test
public void testMakerchekerInboxList() {
public void testMakercheckerInboxList() {
// given
// when
final ArrayList<GetMakerCheckerResponse> makerCheckerList = this.makercheckersHelper.getMakerCheckerList();
List<Map<String, Object>> makerCheckerList = this.makercheckersHelper.getMakerCheckerList(null);
assertNotNull(makerCheckerList);
}
@Test
public void testMakerCheckerOn() {
GlobalConfigurationPropertyData mcConfig = GlobalConfigurationHelper.getGlobalConfigurationByName(requestSpec, responseSpec,
"maker-checker");
Long mcConfigId = mcConfig.getId();
boolean mcConfigUpdate = false;
if (!Boolean.TRUE.equals(mcConfig.getEnabled())) {
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, mcConfigId, true);
mcConfigUpdate = true;
}
GlobalConfigurationPropertyData sameMcConfig = GlobalConfigurationHelper.getGlobalConfigurationByName(requestSpec, responseSpec,
"enable-same-maker-checker");
Long sameMcConfigId = mcConfig.getId();
boolean sameMcConfigUpdate = false;
if (Boolean.TRUE.equals(sameMcConfig.getEnabled())) {
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, sameMcConfigId, false);
sameMcConfigUpdate = true;
}
try {
// client permission - maker-checker disabled
PutPermissionsRequest putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("CREATE_CLIENT", false);
rolesHelper.updatePermissions(putPermissionsRequest);
putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("ACTIVATE_CLIENT", false);
rolesHelper.updatePermissions(putPermissionsRequest);
Integer roleId = RolesHelper.createRole(requestSpec, responseSpec);
Map<String, Boolean> permissionMap = Map.of("CREATE_CLIENT", true, "CREATE_CLIENT_CHECKER", true, "ACTIVATE_CLIENT", true,
"ACTIVATE_CLIENT_CHECKER", true, "WITHDRAWAL_SAVINGSACCOUNT", true, "WITHDRAWAL_SAVINGSACCOUNT_CHECKER", true);
RolesHelper.addPermissionsToRole(requestSpec, responseSpec, roleId, permissionMap);
final Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
// create maker user
String maker = Utils.uniqueRandomStringGenerator("user", 8);
final Integer makerUserId = (Integer) UserHelper.createUser(this.requestSpec, this.responseSpec, roleId, staffId, maker,
"P4ssw0rd", "resourceId");
// create client - maker-checker disabled
RequestSpecification makerRequestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build()
.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(maker, "P4ssw0rd"));
Integer clientId = ClientHelper.createClient(makerRequestSpec, this.responseSpec);
assertNotNull(clientId);
ClientHelper.verifyClientCreatedOnServer(requestSpec, this.responseSpec, clientId);
final Integer savingsId = createApproveActivateSavingsAccountDailyPosting(clientId, START_DATE_STRING);
assertNotNull(savingsId);
Integer transactionId = (Integer) savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", TRANSACTION_DATE_STRING,
CommonConstants.RESPONSE_RESOURCE_ID);
assertNotNull(transactionId);
// client and saving permission - maker-checker enabled
putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("ACTIVATE_CLIENT", true);
rolesHelper.updatePermissions(putPermissionsRequest);
putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("WITHDRAWAL_SAVINGSACCOUNT", true);
rolesHelper.updatePermissions(putPermissionsRequest);
// create client - maker-checker enabled
clientId = ClientHelper.createClient(makerRequestSpec, this.responseSpec);
assertNull(clientId, "Client is created on the server");
List<Map<String, Object>> auditDetails = makercheckersHelper
.getMakerCheckerList(Map.of("actionName", "CREATE", "entityName", "CLIENT", "makerId", makerUserId.toString()));
assertEquals(1, auditDetails.size(), "More than one command exists");
Long clientCommandId = ((Double) auditDetails.get(0).get("id")).longValue();
// savings withdrawal - maker-checker enabled
SavingsAccountHelper makerSavingsHelper = new SavingsAccountHelper(makerRequestSpec, this.responseSpec);
Integer withdrawalId = (Integer) makerSavingsHelper.withdrawalFromSavingsAccount(savingsId, "100", TRANSACTION_DATE_STRING,
CommonConstants.RESPONSE_RESOURCE_ID);
assertNull(withdrawalId, "Withdrawal performed on the server");
auditDetails = makercheckersHelper.getMakerCheckerList(
Map.of("actionName", "WITHDRAWAL", "entityName", "SAVINGSACCOUNT", "makerId", makerUserId.toString()));
assertEquals(1, auditDetails.size(), "More than one command exists");
Long savingCommandId = ((Double) auditDetails.get(0).get("id")).longValue();
// check by the same user should fail
ResponseSpecification failedResponseSpec = new ResponseSpecBuilder().expectStatusCode(400).build();
MakercheckersHelper.approveMakerCheckerEntry(makerRequestSpec, failedResponseSpec, clientCommandId);
MakercheckersHelper.approveMakerCheckerEntry(makerRequestSpec, failedResponseSpec, savingCommandId);
// create checker user
String checker = Utils.uniqueRandomStringGenerator("user", 8);
final Integer checkerUserId = (Integer) UserHelper.createUser(this.requestSpec, this.responseSpec, roleId, staffId, checker,
"P4ssw0rd", "resourceId");
RequestSpecification checkerRequestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build()
.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(checker, "P4ssw0rd"));
// check by another checker user should succeed
HashMap<?, ?> response = MakercheckersHelper.approveMakerCheckerEntry(checkerRequestSpec, responseSpec, clientCommandId);
assertNotNull(response);
clientId = (Integer) response.get("clientId");
assertNotNull(clientId);
ClientHelper.verifyClientCreatedOnServer(requestSpec, responseSpec, clientId);
response = MakercheckersHelper.approveMakerCheckerEntry(checkerRequestSpec, responseSpec, savingCommandId);
assertNotNull(response);
withdrawalId = (Integer) response.get("resourceId");
assertNotNull(withdrawalId);
// add checker superuser permission - actions are performed in one step
permissionMap = Map.of("CHECKER_SUPER_USER", true);
RolesHelper.addPermissionsToRole(requestSpec, responseSpec, roleId, permissionMap);
clientId = ClientHelper.createClient(makerRequestSpec, this.responseSpec);
assertNotNull(clientId);
ClientHelper.verifyClientCreatedOnServer(requestSpec, this.responseSpec, clientId);
withdrawalId = (Integer) makerSavingsHelper.withdrawalFromSavingsAccount(savingsId, "100", TRANSACTION_DATE_STRING,
CommonConstants.RESPONSE_RESOURCE_ID);
assertNotNull(withdrawalId);
} finally {
if (mcConfigUpdate) {
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, mcConfigId, false);
}
if (sameMcConfigUpdate) {
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, sameMcConfigId, true);
}
PutPermissionsRequest putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("WITHDRAWAL_SAVINGSACCOUNT",
false);
rolesHelper.updatePermissions(putPermissionsRequest);
}
}
private Integer createSavingsProductDailyPosting() {
final String savingsProductJSON = this.savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily()
.withInterestPostingPeriodTypeAsDaily().withInterestCalculationPeriodTypeAsDailyBalance().build();
return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
}
private Integer createApproveActivateSavingsAccountDailyPosting(final Integer clientID, final String startDate) {
final Integer savingsProductID = createSavingsProductDailyPosting();
assertNotNull(savingsProductID);
return savingsAccountHelper.createApproveActivateSavingsAccount(clientID, savingsProductID, startDate);
}
}
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.integrationtests;
import static org.apache.fineract.integrationtests.common.CommonConstants.RESPONSE_RESOURCE_ID;
import static org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper.PAYMENT_TYPE_ID;
import static org.apache.fineract.integrationtests.common.system.DatatableHelper.addDatatableColumn;
import static org.apache.http.HttpStatus.SC_CONFLICT;
@@ -60,7 +61,6 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.integrationtests.common.BatchHelper;
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
@@ -118,7 +118,7 @@ public class SavingsAccountTransactionTest {
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDateString);
assertNotNull(clientID);
final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDateString);
final Integer savingsId = createApproveActivateSavingsAccountDailyPosting(clientID, startDateString);
assertNotNull(savingsId);
performSavingsTransaction(savingsId, "100", depositDate, true);
@@ -176,7 +176,7 @@ public class SavingsAccountTransactionTest {
// creating datatable for client entity
final HashMap<String, Object> columnMap = new HashMap<>();
String datatableName = Utils.uniqueRandomStringGenerator("savings_transaction" + "_", 5).toLowerCase();
String datatableName = Utils.uniqueRandomStringGenerator("dt_savings_transaction_", 5).toLowerCase();
columnMap.put("datatableName", datatableName);
columnMap.put("apptableName", "m_savings_account_transaction");
columnMap.put("multiRow", false);
@@ -284,10 +284,9 @@ public class SavingsAccountTransactionTest {
private void performSavingsTransaction(Integer savingsId, String amount, LocalDate transactionDate, boolean isDeposit) {
String transactionType = isDeposit ? "Deposit" : "Withdrawal";
Integer transactionId = isDeposit
? (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, amount, depositDateString,
CommonConstants.RESPONSE_RESOURCE_ID)
? (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, amount, depositDateString, RESPONSE_RESOURCE_ID)
: (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, amount, withdrawDateString,
CommonConstants.RESPONSE_RESOURCE_ID);
RESPONSE_RESOURCE_ID);
assertNotNull(transactionId);
@@ -311,17 +310,10 @@ public class SavingsAccountTransactionTest {
return extractedDate;
}
private Integer createSavingsAccountDailyPosting(final Integer clientID, final String startDate) {
private Integer createApproveActivateSavingsAccountDailyPosting(final Integer clientID, final String startDate) {
final Integer savingsProductID = createSavingsProductDailyPosting();
assertNotNull(savingsProductID);
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplicationOnDate(clientID, savingsProductID,
ACCOUNT_TYPE_INDIVIDUAL, startDate);
assertNotNull(savingsId);
HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavingsOnDate(savingsId, startDate);
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
savingsStatusHashMap = this.savingsAccountHelper.activateSavingsAccount(savingsId, startDate);
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
return savingsId;
return savingsAccountHelper.createApproveActivateSavingsAccount(clientID, savingsProductID, startDate);
}
private Integer createSavingsProductDailyPosting() {
@@ -382,28 +374,31 @@ public class SavingsAccountTransactionTest {
final List<BatchResponse> responses = enclosingTransaction
? BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, json)
: BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec, responseSpec, json);
assertNotNull(responses);
assertNotNull(responses, "Responses");
if (enclosingTransaction) {
Integer statusCode1 = responses.get(0).getStatusCode();
assertNotNull(statusCode1);
assertNotNull(statusCode1, "First enlosingTransaction response status code");
assertTrue(SC_OK == statusCode1 || SC_CONFLICT == statusCode1, "Status code: " + statusCode1);
if (SC_OK == statusCode1) {
assertEquals(4, responses.size());
assertEquals(4, responses.size(), "Response size for enlosingTransaction OK response");
Integer statusCode4 = responses.get(3).getStatusCode();
assertNotNull(statusCode4);
assertEquals(SC_OK, statusCode4);
assertNotNull(statusCode4, "Last enlosingTransaction OK response status code");
assertEquals(SC_OK, statusCode4, "Last enlosingTransaction OK response status code");
} else {
assertEquals(1, responses.size());
assertEquals(1, responses.size(), "Response size for enlosingTransaction failed response");
}
} else {
assertEquals(4, responses.size());
assertEquals(4, responses.size(), "Response size for without-enlosingTransaction response");
Integer statusCode1 = responses.get(0).getStatusCode();
assertNotNull(statusCode1);
assertTrue(SC_OK == statusCode1 || SC_CONFLICT == statusCode1, "Status code: " + statusCode1);
assertNotNull(statusCode1, "First without-enlosingTransaction response status code");
assertTrue(SC_OK == statusCode1 || SC_CONFLICT == statusCode1,
"First without-enlosingTransaction response status code: " + statusCode1);
Integer statusCode4 = responses.get(3).getStatusCode();
assertNotNull(statusCode4);
assertTrue(SC_OK == statusCode1 ? (SC_OK == statusCode4 || SC_CONFLICT == statusCode4)
: (SC_FORBIDDEN == statusCode4 || SC_CONFLICT == statusCode4), "Status code: " + statusCode4);
assertNotNull(statusCode4, "Last without-enlosingTransaction response status code");
assertTrue(
SC_OK == statusCode1 ? (SC_OK == statusCode4 || SC_CONFLICT == statusCode4)
: (SC_FORBIDDEN == statusCode4 || SC_CONFLICT == statusCode4),
"Last without-enlosingTransaction response status code: " + statusCode4);
}
} else {
String json = transactionData.getJson();
@@ -417,14 +412,14 @@ public class SavingsAccountTransactionTest {
}
private static boolean checkConcurrentResponse(String response) {
assertNotNull(response);
assertNotNull(response, "Single response");
JsonPath res = JsonPath.from(response);
String statusCode = res.get("httpStatusCode");
if (statusCode == null) {
assertNotNull(res.get(CommonConstants.RESPONSE_RESOURCE_ID));
assertNotNull(res.get(RESPONSE_RESOURCE_ID), "Single response " + RESPONSE_RESOURCE_ID);
return true;
}
assertEquals(String.valueOf(SC_CONFLICT), statusCode);
assertEquals(String.valueOf(SC_CONFLICT), statusCode, "Single response status code");
return false;
}
}
@@ -439,22 +434,22 @@ public class SavingsAccountTransactionTest {
RequestSpecification requestSpec = savingsHelper.getRequestSpec();
ResponseSpecification responseSpec = savingsHelper.getResponseSpec();
final List<BatchResponse> responses = BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, json);
assertNotNull(responses);
assertNotNull(responses, "Responses");
BatchResponse response1 = responses.get(0);
Integer statusCode = response1.getStatusCode();
String msg = Strings.nullToEmpty(response1.getBody());
assertNotNull(statusCode);
assertNotNull(statusCode, "First response status code");
assertTrue(
SC_OK == statusCode || SC_CONFLICT == statusCode
|| (SC_FORBIDDEN == statusCode && msg.contains("Cannot add or update a child row")),
"Status code: " + statusCode + ", message: " + msg);
if (SC_OK == statusCode) {
assertEquals(4, responses.size());
assertEquals(4, responses.size(), "Response size for OK response");
Integer statusCode4 = responses.get(3).getStatusCode();
assertNotNull(statusCode4);
assertEquals(SC_OK, statusCode4);
assertNotNull(statusCode4, "Last OK response status code");
assertEquals(SC_OK, statusCode4, "Last OK response status code");
} else {
assertEquals(1, responses.size());
assertEquals(1, responses.size(), "Response size for failed response");
}
}
@@ -20,6 +20,7 @@ package org.apache.fineract.integrationtests.common;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.google.gson.Gson;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.Collections;
@@ -27,6 +28,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import org.apache.fineract.client.util.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,13 +40,15 @@ import org.slf4j.LoggerFactory;
public class AuditHelper {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private static final Logger LOG = LoggerFactory.getLogger(AuditHelper.class);
private static final String AUDIT_BASE_URL = "/fineract-provider/api/v1/audits?" + Utils.TENANT_IDENTIFIER;
private static final String AUDITSEARCH_BASE_URL = "/fineract-provider/api/v1/audits/searchtemplate?" + Utils.TENANT_IDENTIFIER;
private static final Gson GSON = new JSON().getGson();
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
public AuditHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
this.requestSpec = requestSpec;
this.responseSpec = responseSpec;
@@ -119,8 +119,8 @@ public class GlobalConfigurationHelper {
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec);
Assertions.assertEquals(52, expectedGlobalConfigurations.size());
Assertions.assertEquals(52, actualGlobalConfigurations.size());
Assertions.assertEquals(53, expectedGlobalConfigurations.size());
Assertions.assertEquals(53, actualGlobalConfigurations.size());
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
@@ -571,9 +571,16 @@ public class GlobalConfigurationHelper {
assetExternalizationOfNonActiveLoans.put("value", 0);
assetExternalizationOfNonActiveLoans.put("enabled", true);
assetExternalizationOfNonActiveLoans.put("trapDoor", false);
assetExternalizationOfNonActiveLoans.put("string_value", "due-date");
defaults.add(assetExternalizationOfNonActiveLoans);
HashMap<String, Object> enableSameMakerChecker = new HashMap<>();
enableSameMakerChecker.put("id", 58);
enableSameMakerChecker.put("name", "enable-same-maker-checker");
enableSameMakerChecker.put("value", 0);
enableSameMakerChecker.put("enabled", false);
enableSameMakerChecker.put("trapDoor", false);
defaults.add(enableSameMakerChecker);
return defaults;
}
@@ -23,8 +23,9 @@ import com.linecorp.armeria.internal.shaded.guava.reflect.TypeToken;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.lang.reflect.Type;
import java.util.ArrayList;
import org.apache.fineract.client.models.GetMakerCheckerResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.common.Utils;
@@ -32,7 +33,7 @@ public class MakercheckersHelper {
private static final Gson GSON = new JSON().getGson();
private static final String MAKERCHECKER_URL = "/fineract-provider/api/v1/makercheckers?" + Utils.TENANT_IDENTIFIER;
private static final String MAKERCHECKER_URL = "/fineract-provider/api/v1/makercheckers";
private final RequestSpecification requestSpec;
private final ResponseSpecification responseSpec;
@@ -42,10 +43,25 @@ public class MakercheckersHelper {
this.responseSpec = responseSpec;
}
public ArrayList<GetMakerCheckerResponse> getMakerCheckerList() {
final String response = Utils.performServerGet(this.requestSpec, this.responseSpec, MAKERCHECKER_URL);
Type makerCheckerList = new TypeToken<ArrayList<GetMakerCheckerResponse>>() {}.getType();
public List<Map<String, Object>> getMakerCheckerList(Map<String, String> queryParams) {
StringBuilder url = new StringBuilder(MAKERCHECKER_URL).append("?").append(Utils.TENANT_IDENTIFIER);
if (queryParams != null) {
for (Map.Entry<String, String> entry : queryParams.entrySet()) {
url.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
}
final String response = Utils.performServerGet(this.requestSpec, this.responseSpec, url.toString());
Type makerCheckerList = new TypeToken<List<Map<String, Object>>>() {}.getType();
return GSON.fromJson(response, makerCheckerList);
}
public void approveMakerCheckerEntry(Long auditId) {
approveMakerCheckerEntry(this.requestSpec, this.responseSpec, auditId);
}
public static HashMap<?, ?> approveMakerCheckerEntry(RequestSpecification requestSpec, ResponseSpecification responseSpec,
Long auditId) {
String url = MAKERCHECKER_URL + "/" + auditId + "?command=approve&" + Utils.TENANT_IDENTIFIER;
return Utils.performServerPost(requestSpec, responseSpec, url, "", "");
}
}
@@ -20,6 +20,7 @@ package org.apache.fineract.integrationtests.common.savings;
import static org.apache.fineract.integrationtests.common.Utils.DEFAULT_TENANT;
import static org.apache.fineract.integrationtests.common.Utils.TENANT_PARAM_NAME;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
@@ -174,6 +175,16 @@ public class SavingsAccountHelper extends IntegrationTest {
savingsApplicationJSON, responseAttribute);
}
public Integer createApproveActivateSavingsAccount(final Integer clientId, Integer savingsProductId, final String startDate) {
final Integer savingsId = applyForSavingsApplicationOnDate(clientId, savingsProductId, ACCOUNT_TYPE_INDIVIDUAL, startDate);
assertNotNull(savingsId);
HashMap savingsStatusHashMap = approveSavingsOnDate(savingsId, startDate);
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
savingsStatusHashMap = activateSavingsAccount(savingsId, startDate);
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
return savingsId;
}
public HashMap updateSavingsAccount(final Integer id, final Integer savingsProductID, final Integer savingsId,
final String accountType) {
final String savingsApplicationJSON = new SavingsApplicationTestBuilder() //
@@ -19,24 +19,36 @@
package org.apache.fineract.integrationtests.useradministration.roles;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.fineract.client.models.CommandProcessingResult;
import org.apache.fineract.client.models.PutPermissionsRequest;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.client.IntegrationTest;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.useradministration.data.PermissionData;
public final class RolesHelper {
public final class RolesHelper extends IntegrationTest {
public static final long SUPER_USER_ROLE_ID = 1L; // This is hardcoded into the initial Liquibase migration
private RolesHelper() {
public RolesHelper() {
}
private static final String CREATE_ROLE_URL = "/fineract-provider/api/v1/roles?" + Utils.TENANT_IDENTIFIER;
private static final String ROLE_URL = "/fineract-provider/api/v1/roles";
private static final String PERMISSIONS_URL = "/fineract-provider/api/v1/permissions";
private static final String DISABLE_ROLE_COMMAND = "disable";
private static final String ENABLE_ROLE_COMMAND = "enable";
private static final Gson GSON = new JSON().getGson();
public static Integer createRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
return Utils.performServerPost(requestSpec, responseSpec, CREATE_ROLE_URL, getTestCreateRoleAsJSON(), "resourceId");
}
@@ -70,13 +82,25 @@ public final class RolesHelper {
}
public static String addPermissionsToRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer roleId, final HashMap<String, Boolean> permissionMap) {
final Integer roleId, final Map<String, Boolean> permissionMap) {
return Utils.performServerPut(requestSpec, responseSpec, ROLE_URL + "/" + roleId + "/permissions?" + Utils.TENANT_IDENTIFIER,
getAddPermissionsToRoleJSON(permissionMap));
}
private static String getAddPermissionsToRoleJSON(HashMap<String, Boolean> permissionMap) {
final HashMap<String, HashMap<String, Boolean>> map = new HashMap<>();
public static List<PermissionData> getPermissions(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
boolean makerCheckerable) {
String response = Utils.performServerGet(requestSpec, responseSpec,
PERMISSIONS_URL + "?" + makerCheckerable + "=" + makerCheckerable);
final Type listType = new TypeToken<List<PermissionData>>() {}.getType();
return GSON.fromJson(response, listType);
}
public CommandProcessingResult updatePermissions(PutPermissionsRequest request) {
return ok(fineract().permissions.updatePermissionsDetails(request));
}
private static String getAddPermissionsToRoleJSON(Map<String, Boolean> permissionMap) {
final HashMap<String, Map<String, Boolean>> map = new HashMap<>();
map.put("permissions", permissionMap);
return new Gson().toJson(map);
}
@@ -84,5 +108,4 @@ public final class RolesHelper {
private static String createRoleOperationURL(final String command, final Integer roleId) {
return ROLE_URL + "/" + roleId + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
}
}