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.exception.ErrorInfo;
import org.apache.fineract.batch.service.ResolutionHelper.BatchRequestNode; import org.apache.fineract.batch.service.ResolutionHelper.BatchRequestNode;
import org.apache.fineract.infrastructure.core.domain.BatchRequestContextHolder; 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.exception.ErrorHandler;
import org.apache.fineract.infrastructure.core.filters.BatchCallHandler; import org.apache.fineract.infrastructure.core.filters.BatchCallHandler;
import org.apache.fineract.infrastructure.core.filters.BatchFilter; 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.dao.NonTransientDataAccessException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionExecution; import org.springframework.transaction.TransactionExecution;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
@@ -104,8 +101,7 @@ public class BatchApiServiceImpl implements BatchApiService {
*/ */
@Override @Override
public List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> requestList, UriInfo uriInfo) { public List<BatchResponse> handleBatchRequestsWithoutEnclosingTransaction(final List<BatchRequest> requestList, UriInfo uriInfo) {
BatchRequestContextHolder.setEnclosingTransaction(Optional.empty()); return handleBatchRequests(requestList, uriInfo, false);
return handleBatchRequests(false, requestList, uriInfo);
} }
/** /**
@@ -117,7 +113,18 @@ public class BatchApiServiceImpl implements BatchApiService {
*/ */
@Override @Override
public List<BatchResponse> handleBatchRequestsWithEnclosingTransaction(final List<BatchRequest> requestList, final UriInfo uriInfo) { 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); TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionConfigurator.accept(transactionTemplate); transactionConfigurator.accept(transactionTemplate);
return transactionTemplate.execute(status -> { return transactionTemplate.execute(status -> {
BatchRequestContextHolder.setEnclosingTransaction(Optional.of(status)); BatchRequestContextHolder.setEnclosingTransaction(status);
try { try {
responseList.addAll(request.get()); responseList.addAll(request.get());
return responseList; return responseList;
} catch (BatchExecutionException ex) { } catch (BatchExecutionException ex) {
status.setRollbackOnly(); responseList.add(buildErrorResponse(ex.getCause(), ex.getRequest()));
return List.of(buildErrorResponse(ex.getCause(), ex.getRequest())); return responseList;
} catch (RuntimeException ex) {
status.setRollbackOnly();
return buildErrorResponses(ex, responseList);
} finally { } finally {
BatchRequestContextHolder.setEnclosingTransaction(Optional.empty()); BatchRequestContextHolder.resetTransaction();
} }
}); });
} catch (TransactionException | NonTransientDataAccessException ex) { } catch (TransactionException | NonTransientDataAccessException ex) {
@@ -163,24 +167,17 @@ public class BatchApiServiceImpl implements BatchApiService {
* @param uriInfo * @param uriInfo
* @return {@code List<BatchResponse>} * @return {@code List<BatchResponse>}
*/ */
private List<BatchResponse> handleBatchRequests(boolean enclosingTransaction, final List<BatchRequest> requestList, private List<BatchResponse> handleRequestNodes(final List<BatchRequest> requestList, final UriInfo uriInfo) {
final UriInfo uriInfo) {
final List<BatchRequestNode> rootNodes; final List<BatchRequestNode> rootNodes;
try { try {
rootNodes = this.resolutionHelper.buildNodesTree(requestList); rootNodes = this.resolutionHelper.buildNodesTree(requestList);
} catch (BatchReferenceInvalidException e) { } catch (BatchReferenceInvalidException e) {
return List.of(buildErrorResponse(e)); return List.of(buildOrThrowErrorResponse(e, null));
} }
final ArrayList<BatchResponse> responseList = new ArrayList<>(requestList.size()); final ArrayList<BatchResponse> responseList = new ArrayList<>(requestList.size());
for (BatchRequestNode rootNode : rootNodes) { for (BatchRequestNode rootNode : rootNodes) {
if (enclosingTransaction) { this.callRequestRecursive(rootNode.getRequest(), rootNode, responseList, uriInfo);
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);
}
} }
responseList.sort(Comparator.comparing(BatchResponse::getRequestId)); responseList.sort(Comparator.comparing(BatchResponse::getRequestId));
return responseList; return responseList;
@@ -197,18 +194,10 @@ public class BatchApiServiceImpl implements BatchApiService {
* the collected responses * the collected responses
* @return {@code BatchResponse} * @return {@code BatchResponse}
*/ */
private void callRequestRecursive(BatchRequest request, BatchRequestNode requestNode, List<BatchResponse> responseList, UriInfo uriInfo, private void callRequestRecursive(BatchRequest request, BatchRequestNode requestNode, List<BatchResponse> responseList,
boolean enclosingTransaction) { UriInfo uriInfo) {
// run current node // run current node
BatchResponse response; BatchResponse response = executeRequest(request, uriInfo);
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);
}
responseList.add(response); responseList.add(response);
if (response.getStatusCode() != null && response.getStatusCode() == SC_OK) { if (response.getStatusCode() != null && response.getStatusCode() == SC_OK) {
// run child nodes // run child nodes
@@ -217,13 +206,10 @@ public class BatchApiServiceImpl implements BatchApiService {
BatchRequest resolvedChildRequest; BatchRequest resolvedChildRequest;
try { try {
resolvedChildRequest = this.resolutionHelper.resolveRequest(childRequest, response); resolvedChildRequest = this.resolutionHelper.resolveRequest(childRequest, response);
callRequestRecursive(resolvedChildRequest, childNode, responseList, uriInfo);
} catch (JsonPathException jpex) { } catch (JsonPathException jpex) {
responseList.add(buildErrorResponse(jpex, childRequest)); responseList.add(buildOrThrowErrorResponse(jpex, childRequest));
return;
} catch (RuntimeException ex) {
throw new BatchExecutionException(childRequest, ex);
} }
callRequestRecursive(resolvedChildRequest, childNode, responseList, uriInfo, enclosingTransaction);
}); });
} else { } else {
responseList.addAll(parentRequestFailedRecursive(request, requestNode, response, null)); 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()); log.debug("Batch request: method [{}], relative url [{}]", request.getMethod(), request.getRelativeUrl());
Either<RuntimeException, BatchRequest> preprocessorResult = runPreprocessors(request); Either<RuntimeException, BatchRequest> preprocessorResult = runPreprocessors(request);
if (preprocessorResult.isLeft()) { if (preprocessorResult.isLeft()) {
throw new BatchExecutionException(request, preprocessorResult.getLeft()); return buildOrThrowErrorResponse(preprocessorResult.getLeft(), request);
} else { } else {
request = preprocessorResult.get(); request = preprocessorResult.get();
} }
@@ -253,19 +239,16 @@ public class BatchApiServiceImpl implements BatchApiService {
BatchRequestContextHolder.setRequestAttributes(new HashMap<>(Optional.ofNullable(request.getHeaders()) BatchRequestContextHolder.setRequestAttributes(new HashMap<>(Optional.ofNullable(request.getHeaders())
.map(list -> list.stream().collect(Collectors.toMap(Header::getName, Header::getValue))) .map(list -> list.stream().collect(Collectors.toMap(Header::getName, Header::getValue)))
.orElse(Collections.emptyMap()))); .orElse(Collections.emptyMap())));
BatchCallHandler callHandler = new BatchCallHandler(this.batchFilters, commandStrategy::execute); if (BatchRequestContextHolder.isEnclosingTransaction()) {
Optional<TransactionStatus> transaction = BatchRequestContextHolder.getEnclosingTransaction();
if (transaction.isPresent()) {
entityManager.flush(); entityManager.flush();
} }
BatchCallHandler callHandler = new BatchCallHandler(this.batchFilters, commandStrategy::execute);
final BatchResponse rootResponse = callHandler.serviceCall(request, uriInfo); final BatchResponse rootResponse = callHandler.serviceCall(request, uriInfo);
log.debug("Batch response: status code [{}], method [{}], relative url [{}]", rootResponse.getStatusCode(), request.getMethod(), log.debug("Batch response: status code [{}], method [{}], relative url [{}]", rootResponse.getStatusCode(), request.getMethod(),
request.getRelativeUrl()); request.getRelativeUrl());
return rootResponse; return rootResponse;
} catch (AbstractIdempotentCommandException idempotentException) {
return buildErrorResponse(idempotentException, request);
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
throw new BatchExecutionException(request, ex); return buildOrThrowErrorResponse(ex, request);
} finally { } finally {
BatchRequestContextHolder.resetRequestAttributes(); BatchRequestContextHolder.resetRequestAttributes();
} }
@@ -313,11 +296,6 @@ public class BatchApiServiceImpl implements BatchApiService {
return responseList; return responseList;
} }
@NotNull
private BatchResponse buildErrorResponse(@NotNull Throwable ex) {
return buildErrorResponse(ex, null);
}
/** /**
* Return the response when any exception raised * Return the response when any exception raised
* *
@@ -346,6 +324,15 @@ public class BatchApiServiceImpl implements BatchApiService {
return buildErrorResponse(requestId, statusCode, body, headers); 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 @NotNull
private List<BatchResponse> buildErrorResponses(Throwable ex, @NotNull List<BatchResponse> responseList) { private List<BatchResponse> buildErrorResponses(Throwable ex, @NotNull List<BatchResponse> responseList) {
BatchResponse response = responseList.isEmpty() ? null BatchResponse response = responseList.isEmpty() ? null
@@ -368,9 +355,10 @@ public class BatchApiServiceImpl implements BatchApiService {
} }
if (response != null) { if (response != null) {
requestId = response.getRequestId(); requestId = response.getRequestId();
if (response.getStatusCode() == null || response.getStatusCode() != SC_OK) { Integer responseCode = response.getStatusCode();
if (response.getStatusCode() != null) { if (responseCode == null || responseCode != SC_OK) {
statusCode = response.getStatusCode(); if (responseCode != null) {
statusCode = responseCode;
} }
body = "Transaction is being rolled back. First erroneous request: \n" + new Gson().toJson(response); 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; this.commandAsJson = json;
} }
public AppUser getMaker() {
return maker;
}
public AppUser getChecker() {
return checker;
}
public String getActionName() { public String getActionName() {
return this.actionName; 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; this.unsupportedCommandName = unsupportedCommandName;
} }
public UnsupportedCommandException(final String unsupportedCommandName, String message) {
super(message);
this.unsupportedCommandName = unsupportedCommandName;
}
public String getUnsupportedCommandName() { public String getUnsupportedCommandName() {
return this.unsupportedCommandName; return this.unsupportedCommandName;
} }
@@ -18,7 +18,6 @@
*/ */
package org.apache.fineract.commands.service; package org.apache.fineract.commands.service;
import org.apache.fineract.commands.domain.CommandSource;
import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
@@ -28,8 +27,6 @@ public interface CommandProcessingService {
CommandProcessingResult executeCommand(CommandWrapper wrapper, JsonCommand command, boolean isApprovedByChecker); CommandProcessingResult executeCommand(CommandWrapper wrapper, JsonCommand command, boolean isApprovedByChecker);
CommandProcessingResult logCommand(CommandSource commandSourceResult); boolean validateRollbackCommand(CommandWrapper commandWrapper, AppUser user);
boolean validateCommand(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.CommandSourceRepository;
import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.exception.CommandNotFoundException; 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.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler; import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -95,19 +98,24 @@ public class CommandSourceService {
idempotencyKey); idempotencyKey);
} }
@Transactional(propagation = Propagation.REQUIRED)
public CommandSource getInitialCommandSource(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) { public CommandSource getInitialCommandSource(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
CommandSource commandSourceResult; CommandSource commandSourceResult = CommandSource.fullEntryFrom(wrapper, jsonCommand, maker, idempotencyKey,
if (jsonCommand.commandId() != null) { UNDER_PROCESSING.getValue());
commandSourceResult = commandSourceRepository.findById(jsonCommand.commandId())
.orElseThrow(() -> new CommandNotFoundException(jsonCommand.commandId()));
commandSourceResult.markAsChecked(maker);
} else {
commandSourceResult = CommandSource.fullEntryFrom(wrapper, jsonCommand, maker, idempotencyKey, UNDER_PROCESSING.getValue());
}
if (commandSourceResult.getCommandJson() == null) { if (commandSourceResult.getCommandJson() == null) {
commandSourceResult.setCommandJson("{}"); commandSourceResult.setCommandJson("{}");
} }
return commandSourceResult; 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; package org.apache.fineract.commands.service;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import java.util.Objects;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.commands.domain.CommandSource; 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.domain.CommandWrapper;
import org.apache.fineract.commands.exception.CommandNotAwaitingApprovalException; import org.apache.fineract.commands.exception.CommandNotAwaitingApprovalException;
import org.apache.fineract.commands.exception.CommandNotFoundException; 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.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
@@ -45,10 +48,10 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final CommandProcessingService processAndLogCommandService; private final CommandProcessingService processAndLogCommandService;
private final SchedulerJobRunnerReadService schedulerJobRunnerReadService; private final SchedulerJobRunnerReadService schedulerJobRunnerReadService;
private final ConfigurationDomainService configurationService;
@Override @Override
public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) {
boolean isApprovedByChecker = false; boolean isApprovedByChecker = false;
// check if is update of own account details // check if is update of own account details
@@ -77,7 +80,6 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
@Override @Override
public CommandProcessingResult approveEntry(final Long makerCheckerId) { public CommandProcessingResult approveEntry(final Long makerCheckerId) {
final CommandSource commandSourceInput = validateMakerCheckerTransaction(makerCheckerId); final CommandSource commandSourceInput = validateMakerCheckerTransaction(makerCheckerId);
validateIsUpdateAllowed(); validateIsUpdateAllowed();
@@ -111,16 +113,24 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
} }
private CommandSource validateMakerCheckerTransaction(final Long makerCheckerId) { private CommandSource validateMakerCheckerTransaction(final Long makerCheckerId) {
final CommandSource commandSource = this.commandSourceRepository.findById(makerCheckerId)
final CommandSource commandSourceInput = this.commandSourceRepository.findById(makerCheckerId)
.orElseThrow(() -> new CommandNotFoundException(makerCheckerId)); .orElseThrow(() -> new CommandNotFoundException(makerCheckerId));
if (!commandSourceInput.isMarkedAsAwaitingApproval()) { if (!commandSource.isMarkedAsAwaitingApproval()) {
throw new CommandNotAwaitingApprovalException(makerCheckerId); throw new CommandNotAwaitingApprovalException(makerCheckerId);
} }
AppUser appUser = this.context.authenticatedUser();
this.context.authenticatedUser().validateHasCheckerPermissionTo(commandSourceInput.getPermissionCode()); String permissionCode = commandSource.getPermissionCode();
appUser.validateHasCheckerPermissionTo(permissionCode);
return commandSourceInput; 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() { 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.CommandProcessingResultType;
import org.apache.fineract.commands.domain.CommandSource; import org.apache.fineract.commands.domain.CommandSource;
import org.apache.fineract.commands.domain.CommandWrapper; 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.exception.UnsupportedCommandException;
import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.commands.provider.CommandHandlerProvider; import org.apache.fineract.commands.provider.CommandHandlerProvider;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; 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.BatchRequestContextHolder;
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler; 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.apache.fineract.useradministration.domain.AppUser;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service @Service
@Slf4j @Slf4j
@@ -76,9 +73,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final CommandHandlerProvider commandHandlerProvider; private final CommandHandlerProvider commandHandlerProvider;
private final IdempotencyKeyResolver idempotencyKeyResolver; private final IdempotencyKeyResolver idempotencyKeyResolver;
private final IdempotencyKeyGenerator idempotencyKeyGenerator;
private final CommandSourceService commandSourceService; private final CommandSourceService commandSourceService;
private final ErrorHandler errorHandler;
private final FineractRequestContextHolder fineractRequestContextHolder; private final FineractRequestContextHolder fineractRequestContextHolder;
private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
@@ -92,73 +87,68 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
Long commandId = (Long) fineractRequestContextHolder.getAttribute(COMMAND_SOURCE_ID, null); Long commandId = (Long) fineractRequestContextHolder.getAttribute(COMMAND_SOURCE_ID, null);
boolean isRetry = commandId != null; boolean isRetry = commandId != null;
boolean isEnclosingTransaction = BatchRequestContextHolder.isEnclosingTransaction();
CommandSource commandSource = null; CommandSource commandSource = null;
String idempotencyKey; String idempotencyKey;
if (isRetry) { if (isRetry) {
commandSource = commandSourceService.getCommandSource(commandId); commandSource = commandSourceService.getCommandSource(commandId);
idempotencyKey = commandSource.getIdempotencyKey(); idempotencyKey = commandSource.getIdempotencyKey();
} else if ((commandId = command.commandId()) != null) { // action on the command itself
commandSource = commandSourceService.getCommandSource(commandId);
idempotencyKey = commandSource.getIdempotencyKey();
} else { } else {
idempotencyKey = idempotencyKeyResolver.resolve(wrapper); idempotencyKey = idempotencyKeyResolver.resolve(wrapper);
} }
exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey, isRetry); exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey, isRetry);
boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent(); AppUser user = context.authenticatedUser(wrapper);
if (commandSource == null) { if (commandSource == null) {
AppUser user = context.authenticatedUser(wrapper); if (isEnclosingTransaction) {
if (sameTransaction) {
commandSource = commandSourceService.getInitialCommandSource(wrapper, command, user, idempotencyKey); commandSource = commandSourceService.getInitialCommandSource(wrapper, command, user, idempotencyKey);
} else { } else {
commandSource = commandSourceService.saveInitialNewTransaction(wrapper, command, user, idempotencyKey); 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); setIdempotencyKeyStoreFlag(true);
final CommandProcessingResult result; final CommandProcessingResult result;
try { try {
result = findCommandHandler(wrapper).processCommand(command); result = commandSourceService.processCommand(findCommandHandler(wrapper), command, commandSource, user, isApprovedByChecker,
isMakerChecker);
} catch (Throwable t) { // NOSONAR } catch (Throwable t) { // NOSONAR
RuntimeException mappable = ErrorHandler.getMappable(t); RuntimeException mappable = ErrorHandler.getMappable(t);
ErrorInfo errorInfo = commandSourceService.generateErrorInfo(mappable); ErrorInfo errorInfo = commandSourceService.generateErrorInfo(mappable);
commandSource.setResultStatusCode(errorInfo.getStatusCode()); Integer statusCode = errorInfo.getStatusCode();
commandSource.setResultStatusCode(statusCode);
commandSource.setResult(errorInfo.getMessage()); commandSource.setResult(errorInfo.getMessage());
commandSource.setStatus(ERROR); if (statusCode != SC_OK) {
if (!sameTransaction) { // TODO: temporary solution commandSource.setStatus(ERROR);
}
if (!isEnclosingTransaction) { // TODO: temporary solution
commandSource = commandSourceService.saveResultNewTransaction(commandSource); 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; throw mappable;
} }
commandSource.updateForAudit(result);
commandSource.setResultStatusCode(SC_OK); commandSource.setResultStatusCode(SC_OK);
commandSource.updateForAudit(result);
commandSource.setResult(toApiJsonSerializer.serializeResult(result)); commandSource.setResult(toApiJsonSerializer.serializeResult(result));
commandSource.setStatus(PROCESSED); 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); commandSource = commandSourceService.saveResultSameTransaction(commandSource);
if (sameTransaction) { storeCommandIdInContext(commandSource); // Store command id as a request attribute
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);
}
result.setRollbackTransaction(null); result.setRollbackTransaction(null);
publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, result); // TODO must be performed in a 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); 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") @SuppressWarnings("unused")
public CommandProcessingResult fallbackExecuteCommand(Exception e) { public CommandProcessingResult fallbackExecuteCommand(Exception e) {
if (e instanceof RollbackTransactionAsCommandIsNotApprovedByCheckerException ex) {
return logCommand(ex.getCommandSourceResult());
}
throw ErrorHandler.getMappable(e); throw ErrorHandler.getMappable(e);
} }
@@ -281,10 +255,10 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} }
@Override @Override
public boolean validateCommand(final CommandWrapper commandWrapper, final AppUser user) { public boolean validateRollbackCommand(final CommandWrapper commandWrapper, final AppUser user) {
boolean rollbackTransaction = configurationDomainService.isMakerCheckerEnabledForTask(commandWrapper.taskPermissionName());
user.validateHasPermissionTo(commandWrapper.getTaskPermissionName()); 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) { 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 isMakerCheckerEnabledForTask(String taskPermissionCode);
boolean isSameMakerCheckerEnabled();
boolean isAmazonS3Enabled(); boolean isAmazonS3Enabled();
boolean isRescheduleFutureRepaymentsEnabled(); boolean isRescheduleFutureRepaymentsEnabled();
@@ -27,15 +27,16 @@ public final class BatchRequestContextHolder {
private 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 @Override
protected Optional<TransactionStatus> initialValue() { protected Optional<TransactionStatus> initialValue() {
return Optional.empty(); return Optional.empty();
} }
}; };
private static final ThreadLocal<Boolean> isEnclosingTransaction = new NamedThreadLocal<>("isEnclosingTransaction");
/** /**
* True if the batch attributes are set * 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() { public static Map<String, Object> getRequestAttributes() {
return enclosingTransaction.get(); return batchAttributes.get();
} }
/** /**
@@ -65,15 +66,6 @@ public final class BatchRequestContextHolder {
batchAttributes.set(requestAttributes); 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. * 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 * @param enclosingTransaction
*/ */
public static void setEnclosingTransaction(Optional<TransactionStatus> enclosingTransaction) { public static void setTransaction(TransactionStatus enclosingTransaction) {
BatchRequestContextHolder.enclosingTransaction.set(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 List<ApiParameterError> errors = new ArrayList<>();
final StringBuilder validationErrorCode = new StringBuilder("error.msg.command.unsupported"); final StringBuilder validationErrorCode = new StringBuilder("error.msg.command.unsupported");
String message = exception.getMessage();
final StringBuilder defaultEnglishMessage = new StringBuilder("The command ").append(exception.getUnsupportedCommandName()) final StringBuilder defaultEnglishMessage = new StringBuilder("The command ").append(exception.getUnsupportedCommandName())
.append(" is not supported."); .append(" is not supported.");
if (message != null) {
defaultEnglishMessage.append(" ").append(message);
}
log.warn("Exception: {}, Message: {}", exception.getClass().getName(), defaultEnglishMessage); log.warn("Exception: {}, Message: {}", exception.getClass().getName(), defaultEnglishMessage);
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(),
exception.getUnsupportedCommandName(), exception.getUnsupportedCommandName()); exception.getUnsupportedCommandName(), exception.getUnsupportedCommandName());
@@ -25,7 +25,6 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.domain.CommandSourceRepository; import org.apache.fineract.commands.domain.CommandSourceRepository;
import org.apache.fineract.commands.service.CommandSourceService; import org.apache.fineract.commands.service.CommandSourceService;
import org.apache.fineract.commands.service.SynchronousCommandProcessingService; 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.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -39,11 +38,9 @@ public class IdempotencyStoreHelper {
public void storeCommandResult(Integer response, String body, Long commandId) { public void storeCommandResult(Integer response, String body, Long commandId) {
commandSourceRepository.findById(commandId).ifPresent(commandSource -> { commandSourceRepository.findById(commandId).ifPresent(commandSource -> {
boolean sameTransaction = BatchRequestContextHolder.getEnclosingTransaction().isPresent();
commandSource.setResultStatusCode(response); commandSource.setResultStatusCode(response);
commandSource.setResult(body); commandSource.setResult(body);
commandSource = sameTransaction ? commandSourceService.saveResultSameTransaction(commandSource) commandSourceService.saveResultSameTransaction(commandSource);
: commandSourceService.saveResultNewTransaction(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) { public void validateHasDatatableReadPermission(final String datatable) {
if (hasNotPermissionForDatatable(datatable, "READ")) { if (hasNotPermissionForDatatable(datatable, "READ")) {
throw new NoAuthorizationException("Not authorised to read datatable: " + datatable); 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.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.exceptionmapper.IdempotentCommandExceptionMapper; import org.apache.fineract.infrastructure.core.exceptionmapper.IdempotentCommandExceptionMapper;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -48,6 +49,11 @@ public class IdempotencyCommandProcessFailedExceptionTest {
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate))); ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void testInconsistentStatus() { public void testInconsistentStatus() {
IdempotentCommandExceptionMapper mapper = new IdempotentCommandExceptionMapper(); 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.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -90,6 +91,11 @@ public class LoanAccountOwnerTransferBusinessStepTest {
externalAssetOwnerTransferLoanMappingRepository, accountingService, businessEventNotifierService); externalAssetOwnerTransferLoanMappingRepository, accountingService, businessEventNotifierService);
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void givenLoanNoTransfer() { public void givenLoanNoTransfer() {
// given // 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.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; 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.SavingsAccountChargeData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData; import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsActivation; import org.apache.fineract.portfolio.savings.data.SavingsActivation;
@@ -147,7 +148,9 @@ public class SavingsImportHandler implements ImportHandler {
Long interestPostingPeriodTypeId = null; Long interestPostingPeriodTypeId = null;
EnumOptionData interestPostingPeriodTypeEnum = null; EnumOptionData interestPostingPeriodTypeEnum = null;
if (interestPostingPeriodType != null) { if (interestPostingPeriodType != null) {
if (interestPostingPeriodType.equalsIgnoreCase(MONTHLY)) { if (interestPostingPeriodType.equalsIgnoreCase(DAILY)) {
interestPostingPeriodTypeId = SavingsPostingInterestPeriodType.DAILY.getValue().longValue();
} else if (interestPostingPeriodType.equalsIgnoreCase(MONTHLY)) {
interestPostingPeriodTypeId = 4L; interestPostingPeriodTypeId = 4L;
} else if (interestPostingPeriodType.equalsIgnoreCase(QUARTERLY)) { } else if (interestPostingPeriodType.equalsIgnoreCase(QUARTERLY)) {
interestPostingPeriodTypeId = 5L; interestPostingPeriodTypeId = 5L;
@@ -18,6 +18,7 @@
*/ */
package org.apache.fineract.infrastructure.configuration.domain; package org.apache.fineract.infrastructure.configuration.domain;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -74,6 +75,11 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
return false; return false;
} }
@Override
public boolean isSameMakerCheckerEnabled() {
return getGlobalConfigurationPropertyData("enable-same-maker-checker").isEnabled();
}
@Override @Override
public boolean isAmazonS3Enabled() { public boolean isAmazonS3Enabled() {
return getGlobalConfigurationPropertyData("amazon-S3").isEnabled(); return getGlobalConfigurationPropertyData("amazon-S3").isEnabled();
@@ -379,18 +385,14 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
return property.getValue(); return property.getValue();
} }
@NotNull
private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) { private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) {
return globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName).toData(); return globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName).toData();
} }
@Override @Override
public boolean isSubRatesEnabled() { public boolean isSubRatesEnabled() {
GlobalConfigurationPropertyData configuration = getGlobalConfigurationPropertyData("sub-rates"); return getGlobalConfigurationPropertyData("sub-rates").isEnabled();
if (configuration == null) {
return false;
} else {
return configuration.isEnabled();
}
} }
@Override @Override
@@ -294,7 +294,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
validateParentGroupRulesBeforeClientActivation(newClient); validateParentGroupRulesBeforeClientActivation(newClient);
runEntityDatatableCheck(newClient.getId(), newClient.getLegalForm()); runEntityDatatableCheck(newClient.getId(), newClient.getLegalForm());
final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateClient(null).build(); final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateClient(null).build();
rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser); rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser);
} }
this.clientRepository.saveAndFlush(newClient); this.clientRepository.saveAndFlush(newClient);
@@ -172,16 +172,16 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
if (newGroup.isCenter()) { if (newGroup.isCenter()) {
final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateCenter(null).build(); final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateCenter(null).build();
rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser); rollbackTransaction = this.commandProcessingService.validateRollbackCommand(commandWrapper, currentUser);
} else { } else {
final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateGroup(null).build(); 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()) { if (!newGroup.isCenter() && newGroup.hasActiveClients()) {
final CommandWrapper commandWrapper = new CommandWrapperBuilder().associateClientsToGroup(newGroup.getId()).build(); 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 // pre-save to generate id for use in group hierarchy
@@ -656,7 +656,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
public CommandProcessingResult createActiveApplication(final SavingsAccountDataDTO savingsAccountDataDTO) { public CommandProcessingResult createActiveApplication(final SavingsAccountDataDTO savingsAccountDataDTO) {
final CommandWrapper commandWrapper = new CommandWrapperBuilder().savingsAccountActivation(null).build(); 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(), final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsAccountDataDTO.getClient(),
savingsAccountDataDTO.getGroup(), savingsAccountDataDTO.getSavingsProduct(), savingsAccountDataDTO.getApplicationDate(), savingsAccountDataDTO.getGroup(), savingsAccountDataDTO.getSavingsProduct(), savingsAccountDataDTO.getApplicationDate(),
@@ -150,4 +150,5 @@
<include file="parts/0128_savings_audit.xml" relativeToChangelogFile="true" /> <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/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/0130_add_create_delinquency_action_permission.xml" relativeToChangelogFile="true" />
<include file="parts/0131_add_configuration_maker_checker.xml" relativeToChangelogFile="true" />
</databaseChangeLog> </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", () -> { When("LoanItemListener.onReadError method executed", () -> {
loanItemListener.onReadError(exception); try {
loanItemListener.onReadError(exception);
} finally {
ThreadLocalContextUtil.reset();
}
}); });
Then("LoanItemListener.onReadError result should match", () -> { Then("LoanItemListener.onReadError result should match", () -> {
@@ -87,7 +91,11 @@ public class LoanItemListenerStepDefinitions implements En {
}); });
When("LoanItemListener.onProcessError method executed", () -> { When("LoanItemListener.onProcessError method executed", () -> {
loanItemListener.onProcessError(loan, exception); try {
loanItemListener.onProcessError(loan, exception);
} finally {
ThreadLocalContextUtil.reset();
}
}); });
Then("LoanItemListener.onProcessError result should match", () -> { Then("LoanItemListener.onProcessError result should match", () -> {
@@ -108,7 +116,11 @@ public class LoanItemListenerStepDefinitions implements En {
}); });
When("LoanItemListener.onWriteError method executed", () -> { 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", () -> { 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.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualPlatformService;
import org.junit.Assert; import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -66,6 +67,11 @@ public class AddPeriodicAccrualEntriesBusinessStepTest {
underTest = new AddPeriodicAccrualEntriesBusinessStep(loanAccrualPlatformService); underTest = new AddPeriodicAccrualEntriesBusinessStep(loanAccrualPlatformService);
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void givenLoanWithAccrual() throws MultiException { public void givenLoanWithAccrual() throws MultiException {
// given // given
@@ -122,7 +122,11 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
}); });
When("ApplyLoanLockTasklet.execute method executed", () -> { 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", () -> { 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.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -72,6 +73,11 @@ public class CheckLoanRepaymentDueBusinessStepTest {
underTest = new CheckLoanRepaymentDueBusinessStep(configurationDomainService, businessEventNotifierService); underTest = new CheckLoanRepaymentDueBusinessStep(configurationDomainService, businessEventNotifierService);
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void givenLoanWithInstallmentDueAfterConfiguredDaysWhenStepExecutionThenBusinessEventIsRaised() { public void givenLoanWithInstallmentDueAfterConfiguredDaysWhenStepExecutionThenBusinessEventIsRaised() {
ArgumentCaptor<LoanRepaymentDueBusinessEvent> loanRepaymentDueEvent = ArgumentCaptor.forClass(LoanRepaymentDueBusinessEvent.class); 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.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -69,6 +70,11 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
underTest = new CheckLoanRepaymentOverdueBusinessStep(configurationDomainService, businessEventNotifierService); underTest = new CheckLoanRepaymentOverdueBusinessStep(configurationDomainService, businessEventNotifierService);
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void givenLoanWithInstallmentOverdueAfterConfiguredDaysWhenStepExecutionThenBusinessEventIsRaised() { public void givenLoanWithInstallmentOverdueAfterConfiguredDaysWhenStepExecutionThenBusinessEventIsRaised() {
ArgumentCaptor<LoanRepaymentOverdueBusinessEvent> loanRepaymentDueBusinessEventArgumentCaptor = ArgumentCaptor ArgumentCaptor<LoanRepaymentOverdueBusinessEvent> loanRepaymentDueBusinessEventArgumentCaptor = ArgumentCaptor
@@ -169,5 +175,4 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
assertEquals(repaymentInstallment, loanPayloadForEvent); assertEquals(repaymentInstallment, loanPayloadForEvent);
assertEquals(processedLoan, loanForProcessing); 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.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; 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.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -73,6 +74,11 @@ class LoanItemReaderTest {
@Mock @Mock
private Loan loan; private Loan loan;
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void testLoanItemReaderSimple() throws Exception { public void testLoanItemReaderSimple() throws Exception {
// given // given
@@ -163,5 +169,4 @@ class LoanItemReaderTest {
Mockito.verifyNoMoreInteractions(loanRepository); 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.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -100,6 +101,11 @@ public class SetLoanDelinquencyTagsBusinessStepTest {
delinquencyReadPlatformService, businessEventNotifierService); delinquencyReadPlatformService, businessEventNotifierService);
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
/** /**
* Tests {@link SetLoanDelinquencyTagsBusinessStep#execute(Loan)} success scenario. * Tests {@link SetLoanDelinquencyTagsBusinessStep#execute(Loan)} success scenario.
* *
@@ -85,7 +85,7 @@ public class COBBulkEventConfigurationTest {
@AfterEach @AfterEach
public void tearDown() { public void tearDown() {
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); ThreadLocalContextUtil.reset();
} }
@Test @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.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; 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.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@@ -73,6 +74,11 @@ class InlineLoanCOBExecutorServiceImplTest {
@Mock @Mock
private FineractProperties.FineractBodyItemSizeLimitProperties fineractBodyItemSizeLimitProperties; private FineractProperties.FineractBodyItemSizeLimitProperties fineractBodyItemSizeLimitProperties;
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
void shouldExceptionThrownIfLoanIsAlreadyLocked() { void shouldExceptionThrownIfLoanIsAlreadyLocked() {
JsonCommand command = mock(JsonCommand.class); 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.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -71,6 +72,11 @@ public class PurgeProcessedCommandsTaskletTest {
underTest = new PurgeProcessedCommandsTasklet(repository, configurationDomainService); underTest = new PurgeProcessedCommandsTasklet(repository, configurationDomainService);
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void givenEventsForPurgeWhenTaskExecutionThenEventsPurgeForDaysCriteria() { public void givenEventsForPurgeWhenTaskExecutionThenEventsPurgeForDaysCriteria() {
// given // given
@@ -97,5 +103,4 @@ public class PurgeProcessedCommandsTaskletTest {
// then // then
assertEquals(RepeatStatus.FINISHED, resultStatus); assertEquals(RepeatStatus.FINISHED, resultStatus);
} }
} }
@@ -25,13 +25,10 @@ import static org.mockito.ArgumentMatchers.any;
import io.cucumber.java8.En; import io.cucumber.java8.En;
import io.github.resilience4j.retry.RetryRegistry; import io.github.resilience4j.retry.RetryRegistry;
import io.github.resilience4j.retry.event.RetryEvent; import io.github.resilience4j.retry.event.RetryEvent;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; 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.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.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder; import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
@@ -69,7 +66,7 @@ public class CommandServiceStepDefinitions implements En {
ReflectionTestUtils.setField(processAndLogCommandService, "fineractRequestContextHolder", contextHolder); ReflectionTestUtils.setField(processAndLogCommandService, "fineractRequestContextHolder", contextHolder);
Mockito.when(contextHolder.getAttribute(any(), any())).thenThrow(new CannotAcquireLockException("BLOW IT UP!!!")) Mockito.when(contextHolder.getAttribute(any(), any())).thenThrow(new CannotAcquireLockException("BLOW IT UP!!!"))
.thenThrow(new ObjectOptimisticLockingFailureException("Dummy", new RuntimeException("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 -> { this.retryRegistry.retry("executeCommand").getEventPublisher().onRetry(event -> {
log.warn("... retry event: {}", event); log.warn("... retry event: {}", event);
@@ -147,13 +144,4 @@ public class CommandServiceStepDefinitions implements En {
return null; 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 @Test
public void testCreateFromExisting() { public void testCreateFromExisting() {
CommandWrapper wrapper = CommandWrapper.wrap("act", "ent", 1L, 1L);
long commandId = 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); CommandSource commandMock = Mockito.mock(CommandSource.class);
Mockito.when(commandSourceRepository.saveAndFlush(commandMock)).thenReturn(commandMock);
Mockito.when(commandSourceRepository.findById(commandId)).thenReturn(Optional.of(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.getCommandSource(commandId);
Assertions.assertEquals(commandMock, actual);
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);
} }
@Test @Test
@@ -67,8 +67,6 @@ public class SynchronousCommandProcessingServiceTest {
@Mock @Mock
private IdempotencyKeyResolver idempotencyKeyResolver; private IdempotencyKeyResolver idempotencyKeyResolver;
@Mock @Mock
private IdempotencyKeyGenerator idempotencyKeyGenerator;
@Mock
private CommandSourceService commandSourceService; private CommandSourceService commandSourceService;
@Spy @Spy
@@ -93,19 +91,21 @@ public class SynchronousCommandProcessingServiceTest {
when(commandWrapper.isNoteResource()).thenReturn(false); when(commandWrapper.isNoteResource()).thenReturn(false);
when(commandWrapper.isSurveyResource()).thenReturn(false); when(commandWrapper.isSurveyResource()).thenReturn(false);
when(commandWrapper.isLoanDisburseDetailResource()).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); CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class);
when(commandProcessingResult.isRollbackTransaction()).thenReturn(false); when(commandProcessingResult.isRollbackTransaction()).thenReturn(false);
when(newCommandSourceHandler.processCommand(jsonCommand)).thenReturn(commandProcessingResult); when(commandHandler.processCommand(jsonCommand)).thenReturn(commandProcessingResult);
when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(newCommandSourceHandler); when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(commandHandler);
when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false); when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false);
String idk = "idk"; String idk = "idk";
when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk); when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk);
CommandSource commandSource = Mockito.mock(CommandSource.class); CommandSource commandSource = Mockito.mock(CommandSource.class);
long commandId = 1L;
when(commandSource.getId()).thenReturn(commandId); when(commandSource.getId()).thenReturn(commandId);
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null); when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null);
when(commandSourceService.getCommandSource(commandId)).thenReturn(commandSource); when(commandSourceService.getCommandSource(commandId)).thenReturn(commandSource);
@@ -116,9 +116,12 @@ public class SynchronousCommandProcessingServiceTest {
when(commandSource.getStatus()).thenReturn(CommandProcessingResultType.PROCESSED.getValue()); when(commandSource.getStatus()).thenReturn(CommandProcessingResultType.PROCESSED.getValue());
when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser); 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); CommandProcessingResult actualCommandProcessingResult = underTest.executeCommand(commandWrapper, jsonCommand, false);
verify(commandSourceService).saveInitialNewTransaction(commandWrapper, jsonCommand, appUser, idk); verify(commandSourceService).getCommandSource(commandId);
assertEquals(CommandProcessingResultType.PROCESSED.getValue(), commandSource.getStatus()); assertEquals(CommandProcessingResultType.PROCESSED.getValue(), commandSource.getStatus());
verify(commandSourceService).saveResultSameTransaction(commandSource); verify(commandSourceService).saveResultSameTransaction(commandSource);
@@ -133,19 +136,21 @@ public class SynchronousCommandProcessingServiceTest {
when(commandWrapper.isSurveyResource()).thenReturn(false); when(commandWrapper.isSurveyResource()).thenReturn(false);
when(commandWrapper.isLoanDisburseDetailResource()).thenReturn(false); when(commandWrapper.isLoanDisburseDetailResource()).thenReturn(false);
JsonCommand jsonCommand = Mockito.mock(JsonCommand.class); 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); CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class);
CommandSource commandSource = Mockito.mock(CommandSource.class); CommandSource commandSource = Mockito.mock(CommandSource.class);
when(commandProcessingResult.isRollbackTransaction()).thenReturn(false); when(commandProcessingResult.isRollbackTransaction()).thenReturn(false);
RuntimeException runtimeException = new RuntimeException("foo"); RuntimeException runtimeException = new RuntimeException("foo");
when(newCommandSourceHandler.processCommand(jsonCommand)).thenThrow(runtimeException); when(commandHandler.processCommand(jsonCommand)).thenThrow(runtimeException);
when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(newCommandSourceHandler); when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(commandHandler);
when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false); when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false);
String idk = "idk"; String idk = "idk";
when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk); when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk);
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null); when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null);
when(commandSourceService.getCommandSource(commandId)).thenReturn(commandSource);
AppUser appUser = Mockito.mock(AppUser.class); AppUser appUser = Mockito.mock(AppUser.class);
when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser); 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.findCommandSource(commandWrapper, idk)).thenReturn(initialCommandSource);
when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false, false))
.thenThrow(runtimeException);
Assertions.assertThrows(RuntimeException.class, () -> { Assertions.assertThrows(RuntimeException.class, () -> {
underTest.executeCommand(commandWrapper, jsonCommand, false); underTest.executeCommand(commandWrapper, jsonCommand, false);
}); });
verify(commandSourceService).saveInitialNewTransaction(commandWrapper, jsonCommand, appUser, idk); verify(commandSourceService).getCommandSource(commandId);
verify(commandSourceService).generateErrorInfo(runtimeException); 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.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -79,6 +80,11 @@ public class BusinessDateWritePlatformServiceTest {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void businessDateIsNotEnabled() { public void businessDateIsNotEnabled() {
JsonCommand command = JsonCommand.from(""); 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.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; 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"))))); .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.of("Asia/Kolkata")))));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void markCreated() { public void markCreated() {
MappingContext mappingContext = Mockito.mock(MappingContext.class); MappingContext mappingContext = Mockito.mock(MappingContext.class);
@@ -111,5 +117,4 @@ public class CustomAuditingHandlerTest {
assertEquals(now.getHour(), targetObject.getCreatedDate().get().getHour()); assertEquals(now.getHour(), targetObject.getCreatedDate().get().getHour());
assertEquals(now.getMinute(), targetObject.getCreatedDate().get().getMinute()); 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.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -38,6 +39,11 @@ public class CustomDateTimeProviderTest {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void instanceDateProvider() { public void instanceDateProvider() {
Optional<TemporalAccessor> dateTimeProvider = CustomDateTimeProvider.INSTANCE.getNow(); Optional<TemporalAccessor> dateTimeProvider = CustomDateTimeProvider.INSTANCE.getNow();
@@ -65,5 +71,4 @@ public class CustomDateTimeProviderTest {
assertEquals(now.getHour(), ((OffsetDateTime) dateTimeProvider.get()).getHour()); assertEquals(now.getHour(), ((OffsetDateTime) dateTimeProvider.get()).getHour());
assertEquals(now.getMinute(), ((OffsetDateTime) dateTimeProvider.get()).getMinute()); assertEquals(now.getMinute(), ((OffsetDateTime) dateTimeProvider.get()).getMinute());
} }
} }
@@ -32,6 +32,7 @@ import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; 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.BeforeEach;
import org.junit.jupiter.api.Test; 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)))); ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.of(2022, 6, 12))));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void getSystemZoneIdTest() { public void getSystemZoneIdTest() {
TimeZone.setDefault(TimeZone.getTimeZone("GMT+02:00")); 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.Collections;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; 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.apache.fineract.infrastructure.dataqueries.service.export.DatatableExportUtil;
import org.junit.Assert; 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.Test;
public class DatatableExportUtilTest { public class DatatableExportUtilTest {
@BeforeEach
public void setUp() {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
}
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void emptyFolderTest() { public void emptyFolderTest() {
Assert.assertEquals("", DatatableExportUtil.normalizeFolderName("")); 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.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.event.external.repository.ExternalEventRepository; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -69,6 +70,11 @@ public class PurgeExternalEventsTaskletTest {
underTest = new PurgeExternalEventsTasklet(repository, configurationDomainService); underTest = new PurgeExternalEventsTasklet(repository, configurationDomainService);
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void givenEventsForPurgeWhenTaskExecutionThenEventsPurgeForDaysCriteria() { public void givenEventsForPurgeWhenTaskExecutionThenEventsPurgeForDaysCriteria() {
// given // given
@@ -95,5 +101,4 @@ public class PurgeExternalEventsTaskletTest {
// then // then
assertEquals(RepeatStatus.FINISHED, resultStatus); 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.repository.domain.ExternalEventView;
import org.apache.fineract.infrastructure.event.external.service.message.MessageFactory; import org.apache.fineract.infrastructure.event.external.service.message.MessageFactory;
import org.apache.fineract.infrastructure.event.external.service.support.ByteBufferConverter; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -97,6 +98,11 @@ class SendAsynchronousEventsTaskletTest {
configurationDomainService); configurationDomainService);
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
private void configureExternalEventsProducerReadBatchSizeProperty() { private void configureExternalEventsProducerReadBatchSizeProperty() {
FineractProperties.FineractEventsProperties eventsProperties = new FineractProperties.FineractEventsProperties(); FineractProperties.FineractEventsProperties eventsProperties = new FineractProperties.FineractEventsProperties();
FineractProperties.FineractExternalEventsProperties externalProperties = new FineractProperties.FineractExternalEventsProperties(); 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.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; 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.Test;
class ExternalEventTest { class ExternalEventTest {
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void testConstructorWorks() { public void testConstructorWorks() {
// given // given
@@ -56,6 +62,5 @@ class ExternalEventTest {
assertThat(result.getBusinessDate()).isEqualTo(currentBusinessDate); assertThat(result.getBusinessDate()).isEqualTo(currentBusinessDate);
assertThat(result.getCreatedAt()).isNotNull(); assertThat(result.getCreatedAt()).isNotNull();
assertThat(result.getSentAt()).isNull(); 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.LoanAccountDataV1Enricher;
import org.apache.fineract.investor.enricher.LoanTransactionAdjustmentDataV1Enricher; import org.apache.fineract.investor.enricher.LoanTransactionAdjustmentDataV1Enricher;
import org.apache.fineract.investor.enricher.LoanTransactionDataV1Enricher; import org.apache.fineract.investor.enricher.LoanTransactionDataV1Enricher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; 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())))); .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void testPostEventShouldFailWhenNullEventIsGiven() { public void testPostEventShouldFailWhenNullEventIsGiven() {
// given // 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.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; 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())))); .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void testLoanDelinquencyRangeEventPayloadSerialization() throws IOException { public void testLoanDelinquencyRangeEventPayloadSerialization() throws IOException {
// given // given
@@ -340,5 +346,4 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
ReflectionTestUtils.setField(loanCharge, "id", 1L); ReflectionTestUtils.setField(loanCharge, "id", 1L);
return loanCharge; 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.LoanChargePaidByReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; 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())))); .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
void loanTransactionReversedOnDateSerializationTest() { void loanTransactionReversedOnDateSerializationTest() {
Loan loanForProcessing = Mockito.mock(Loan.class); Loan loanForProcessing = Mockito.mock(Loan.class);
@@ -107,5 +113,4 @@ public class LoanAdjustTransactionBusinessEventSerializerTest {
.toAvroDTO(businessEvent); .toAvroDTO(businessEvent);
assertEquals(reversedLocalDate, loanTransactionAdjustmentDataV1.getTransactionToAdjust().getReversedOnDate()); assertEquals(reversedLocalDate, loanTransactionAdjustmentDataV1.getTransactionToAdjust().getReversedOnDate());
} }
} }
@@ -90,6 +90,7 @@ public class LoanRepaymentBusinessEventSerializerTest {
@AfterEach @AfterEach
public void reset() { public void reset() {
ThreadLocalContextUtil.reset();
moneyHelper.close(); moneyHelper.close();
} }
@@ -181,5 +182,4 @@ public class LoanRepaymentBusinessEventSerializerTest {
AvroRuntimeException exceptionThrown = assertThrows(AvroRuntimeException.class, () -> serializer.toAvroDTO(event)); AvroRuntimeException exceptionThrown = assertThrows(AvroRuntimeException.class, () -> serializer.toAvroDTO(event));
assertTrue(exceptionThrown.getMessage().contains("does not accept null values")); 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.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -99,6 +100,11 @@ class LoanCOBApiFilterTest {
testObj = new LoanCOBApiFilter(helper); testObj = new LoanCOBApiFilter(helper);
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
void shouldLoanAndExternalMatchToo() { void shouldLoanAndExternalMatchToo() {
String externalId = UUID.randomUUID().toString(); 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.LoanAccountDataV1Enricher;
import org.apache.fineract.investor.enricher.LoanChargeDataV1Enricher; import org.apache.fineract.investor.enricher.LoanChargeDataV1Enricher;
import org.apache.fineract.investor.enricher.LoanTransactionDataV1Enricher; import org.apache.fineract.investor.enricher.LoanTransactionDataV1Enricher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -66,6 +67,11 @@ public class EnricherTest {
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void testLoanAccountDataEnricher() { public void testLoanAccountDataEnricher() {
LoanAccountDataV1Enricher loanAccountDataV1Enricher = mock(LoanAccountDataV1Enricher.class); LoanAccountDataV1Enricher loanAccountDataV1Enricher = mock(LoanAccountDataV1Enricher.class);
@@ -178,5 +184,4 @@ public class EnricherTest {
assertEquals(PURCHASE_PRICE_RATIO, data.getPurchasePriceRatio()); 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.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; 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())))); .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void givenLoanAccountWithDelinquencyBucketWhenRangeChangeThenEventIsRaised() { public void givenLoanAccountWithDelinquencyBucketWhenRangeChangeThenEventIsRaised() {
ArgumentCaptor<LoanDelinquencyRangeChangeBusinessEvent> loanDeliquencyRangeChangeEvent = ArgumentCaptor ArgumentCaptor<LoanDelinquencyRangeChangeBusinessEvent> loanDeliquencyRangeChangeEvent = ArgumentCaptor
@@ -614,5 +620,4 @@ public class DelinquencyWritePlatformServiceRangeChangeEventTest {
Loan loanPayloadForEvent = loanDelinquencyRangeChangeEvent.getValue().get(); Loan loanPayloadForEvent = loanDelinquencyRangeChangeEvent.getValue().get();
assertEquals(loanForProcessing, loanPayloadForEvent); assertEquals(loanForProcessing, loanPayloadForEvent);
} }
} }
@@ -105,6 +105,7 @@ public class LoanDelinquencyDomainServiceTest {
@AfterEach @AfterEach
public void deregister() { public void deregister() {
ThreadLocalContextUtil.reset();
moneyHelperStatic.close(); moneyHelperStatic.close();
} }
@@ -27,16 +27,23 @@ import static org.mockito.Mockito.when;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; 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.ExternalId;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils; 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.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
@@ -46,11 +53,21 @@ import org.springframework.test.util.ReflectionTestUtils;
*/ */
public class LoanTest { public class LoanTest {
private final LocalDate actualDate = LocalDate.now(ZoneId.systemDefault());
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
/** /**
* Tests {@link Loan#getCharges()} with charges. * Tests {@link Loan#getCharges()} with charges.
*/ */
@Test @Test
public void testGetChargesWithCharges() { 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(); Loan loan = new Loan();
ReflectionTestUtils.setField(loan, "charges", Collections.singleton(buildLoanCharge())); 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.PaymentAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -83,6 +84,11 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate))); ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate)));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
@Test @Test
public void chargePaymentTransactionTestWithExactAmount() { public void chargePaymentTransactionTestWithExactAmount() {
final BigDecimal chargeAmount = BigDecimal.valueOf(100); 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.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -104,6 +105,11 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate))); ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate)));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
// IN ADVANCE // IN ADVANCE
@Test @Test
public void inAdvancePaymentOfPrincipal() { 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.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -105,6 +106,11 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate))); ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, transactionDate)));
} }
@AfterEach
public void tearDown() {
ThreadLocalContextUtil.reset();
}
// IN ADVANCE // IN ADVANCE
@Test @Test
public void inAdvancePaymentOfPrincipal() { public void inAdvancePaymentOfPrincipal() {
@@ -69,6 +69,7 @@ public class LoanCalculateRepaymentPastDueServiceTest {
@AfterEach @AfterEach
public void reset() { public void reset() {
ThreadLocalContextUtil.reset();
moneyHelper.close(); moneyHelper.close();
} }
@@ -2153,12 +2153,7 @@ public class BatchApiTest {
LOG.info("Batch Response : {}", new Gson().toJson(responseOfQueryAndUpdateDatatableBatch)); LOG.info("Batch Response : {}", new Gson().toJson(responseOfQueryAndUpdateDatatableBatch));
final BatchResponse queryResponse = responseOfQueryAndUpdateDatatableBatch.get(0); final BatchResponse updateResponse = 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);
Assertions.assertEquals(2L, updateResponse.getRequestId()); Assertions.assertEquals(2L, updateResponse.getRequestId());
Assertions.assertEquals(HttpStatus.SC_BAD_REQUEST, updateResponse.getStatusCode(), 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.PAUSE;
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME; 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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
@@ -109,8 +110,10 @@ public class DelinquencyBucketsIntegrationTest {
// then // then
assertNotNull(delinquencyRangeResponse01); assertNotNull(delinquencyRangeResponse01);
assertNotNull(ranges); assertNotNull(ranges);
assertEquals(1, ranges.get(0).getMinimumAgeDays(), "Expected Min Age Days to 1"); assertFalse(ranges.isEmpty());
assertEquals(3, ranges.get(0).getMaximumAgeDays(), "Expected Max Age Days to 3"); 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 @Test
@@ -19,22 +19,25 @@
package org.apache.fineract.integrationtests; package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder; import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.response.Response; import io.restassured.response.Response;
import io.restassured.response.ResponseBody;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification; import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.apache.fineract.cob.data.BusinessStep; import org.apache.fineract.cob.data.BusinessStep;
import org.apache.fineract.cob.data.JobBusinessStepConfigData; import org.apache.fineract.cob.data.JobBusinessStepConfigData;
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException; import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
import org.apache.fineract.integrationtests.common.IdempotencyHelper; import org.apache.fineract.integrationtests.common.IdempotencyHelper;
import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -70,9 +73,9 @@ public class IdempotencyTest {
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Response responseSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME, Response responseSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Assertions.assertEquals(response.getBody().asString(), responseSecond.getBody().asString()); assertEquals(response.getBody().asString(), responseSecond.getBody().asString());
Assertions.assertNull(response.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNull(response.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertNotNull(responseSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNotNull(responseSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
idempotencyKeyHeader = UUID.randomUUID().toString(); idempotencyKeyHeader = UUID.randomUUID().toString();
@@ -89,9 +92,9 @@ public class IdempotencyTest {
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Response updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME, Response updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString()); assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME); newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME);
applyChargeStep = newStepConfig.getBusinessSteps().stream() applyChargeStep = newStepConfig.getBusinessSteps().stream()
@@ -109,9 +112,9 @@ public class IdempotencyTest {
updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME, updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader); IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString()); assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME); newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME);
applyChargeStep = newStepConfig.getBusinessSteps().stream() applyChargeStep = newStepConfig.getBusinessSteps().stream()
@@ -126,14 +129,14 @@ public class IdempotencyTest {
updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME, updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, updateResponseSpec, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(originalStepConfig.getBusinessSteps()), idempotencyKeyHeader); IdempotencyHelper.toJsonString(originalStepConfig.getBusinessSteps()), idempotencyKeyHeader);
Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString()); assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
} }
@Test @Test
public void shoudTheSecondRequestWithSameIdempotencyKeyWillFailureToo() { public void shouldTheSecondRequestWithSameIdempotencyKeyWillFailureToo() {
ResponseSpecification responseSpecForError = new ResponseSpecBuilder().expectStatusCode(400).build(); ResponseSpecification responseSpecForError = new ResponseSpecBuilder().expectStatusCode(400).build();
List<BusinessStep> requestBody = new ArrayList<>(); List<BusinessStep> requestBody = new ArrayList<>();
String idempotencyKey = UUID.randomUUID().toString(); String idempotencyKey = UUID.randomUUID().toString();
@@ -141,13 +144,14 @@ public class IdempotencyTest {
Response response1 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME, Response response1 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKey); IdempotencyHelper.toJsonString(requestBody), idempotencyKey);
Assertions.assertNull(response1.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNull(response1.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
String originalBody = response1.getBody().asString(); ResponseBody body1 = response1.getBody();
assertNotNull(body1);
Response response2 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME, Response response2 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME,
IdempotencyHelper.toJsonString(requestBody), idempotencyKey); IdempotencyHelper.toJsonString(requestBody), idempotencyKey);
Assertions.assertNotNull(response2.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER)); assertNotNull(response2.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
Assertions.assertEquals(originalBody, response2.getBody().asString()); assertEquals((Map) body1.jsonPath().get(""), response2.getBody().jsonPath().get(""));
} }
private BusinessStep getBusinessSteps(Long order, String stepName) { private BusinessStep getBusinessSteps(Long order, String stepName) {
@@ -181,8 +181,8 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN)
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en")); .transactionAmount(BigDecimal.valueOf(1000.00)).locale("en"));
final float feePortion = 50.0f; final double feePortion = 50.0d;
final float penaltyPortion = 100.0f; final double penaltyPortion = 100.0d;
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateWithAccountTransferJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, ChargesHelper.getLoanSpecifiedDueDateWithAccountTransferJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
@@ -209,8 +209,8 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
assertEquals(feePortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding()); assertEquals(feePortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesOutstanding());
assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue()); assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue());
assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding()); assertEquals(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding());
assertEquals(400.0f, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod()); assertEquals(400.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod());
assertEquals(400.0f, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod()); assertEquals(400.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod());
assertEquals(LocalDate.of(2023, 1, 16), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); assertEquals(LocalDate.of(2023, 1, 16), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
scheduleJobHelper.executeAndAwaitJob(jobName); scheduleJobHelper.executeAndAwaitJob(jobName);
@@ -218,11 +218,11 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
loanDetails = loanTransactionHelper.getLoanDetails((long) loanId); loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
assertEquals(5, loanDetails.getRepaymentSchedule().getPeriods().size()); assertEquals(5, loanDetails.getRepaymentSchedule().getPeriods().size());
assertEquals(feePortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getFeeChargesDue()); 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(penaltyPortion, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesDue());
assertEquals(0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding()); assertEquals(0.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPenaltyChargesOutstanding());
assertEquals(400, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod()); assertEquals(400.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalDueForPeriod());
assertEquals(250, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod()); assertEquals(250.0d, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalOutstandingForPeriod());
assertEquals(LocalDate.of(2023, 1, 16), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); assertEquals(LocalDate.of(2023, 1, 16), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate());
} finally { } finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
@@ -18,17 +18,31 @@
*/ */
package org.apache.fineract.integrationtests; 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder; import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification; import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList; import java.util.HashMap;
import org.apache.fineract.client.models.GetMakerCheckerResponse; 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.Utils;
import org.apache.fineract.integrationtests.common.commands.MakercheckersHelper; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -38,6 +52,12 @@ public class MakercheckerTest {
private ResponseSpecification responseSpec; private ResponseSpecification responseSpec;
private RequestSpecification requestSpec; private RequestSpecification requestSpec;
private MakercheckersHelper makercheckersHelper; 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 @BeforeEach
public void setup() { public void setup() {
@@ -46,14 +66,151 @@ public class MakercheckerTest {
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
this.makercheckersHelper = new MakercheckersHelper(this.requestSpec, this.responseSpec); 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 @Test
public void testMakerchekerInboxList() { public void testMakercheckerInboxList() {
// given // given
// when // when
final ArrayList<GetMakerCheckerResponse> makerCheckerList = this.makercheckersHelper.getMakerCheckerList(); List<Map<String, Object>> makerCheckerList = this.makercheckersHelper.getMakerCheckerList(null);
assertNotNull(makerCheckerList); 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; 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.savings.SavingsAccountHelper.PAYMENT_TYPE_ID;
import static org.apache.fineract.integrationtests.common.system.DatatableHelper.addDatatableColumn; import static org.apache.fineract.integrationtests.common.system.DatatableHelper.addDatatableColumn;
import static org.apache.http.HttpStatus.SC_CONFLICT; 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.BatchHelper;
import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.BusinessDateHelper;
import org.apache.fineract.integrationtests.common.ClientHelper; 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.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper; 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); final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDateString);
assertNotNull(clientID); assertNotNull(clientID);
final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDateString); final Integer savingsId = createApproveActivateSavingsAccountDailyPosting(clientID, startDateString);
assertNotNull(savingsId); assertNotNull(savingsId);
performSavingsTransaction(savingsId, "100", depositDate, true); performSavingsTransaction(savingsId, "100", depositDate, true);
@@ -176,7 +176,7 @@ public class SavingsAccountTransactionTest {
// creating datatable for client entity // creating datatable for client entity
final HashMap<String, Object> columnMap = new HashMap<>(); 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("datatableName", datatableName);
columnMap.put("apptableName", "m_savings_account_transaction"); columnMap.put("apptableName", "m_savings_account_transaction");
columnMap.put("multiRow", false); columnMap.put("multiRow", false);
@@ -284,10 +284,9 @@ public class SavingsAccountTransactionTest {
private void performSavingsTransaction(Integer savingsId, String amount, LocalDate transactionDate, boolean isDeposit) { private void performSavingsTransaction(Integer savingsId, String amount, LocalDate transactionDate, boolean isDeposit) {
String transactionType = isDeposit ? "Deposit" : "Withdrawal"; String transactionType = isDeposit ? "Deposit" : "Withdrawal";
Integer transactionId = isDeposit Integer transactionId = isDeposit
? (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, amount, depositDateString, ? (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, amount, depositDateString, RESPONSE_RESOURCE_ID)
CommonConstants.RESPONSE_RESOURCE_ID)
: (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, amount, withdrawDateString, : (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, amount, withdrawDateString,
CommonConstants.RESPONSE_RESOURCE_ID); RESPONSE_RESOURCE_ID);
assertNotNull(transactionId); assertNotNull(transactionId);
@@ -311,17 +310,10 @@ public class SavingsAccountTransactionTest {
return extractedDate; return extractedDate;
} }
private Integer createSavingsAccountDailyPosting(final Integer clientID, final String startDate) { private Integer createApproveActivateSavingsAccountDailyPosting(final Integer clientID, final String startDate) {
final Integer savingsProductID = createSavingsProductDailyPosting(); final Integer savingsProductID = createSavingsProductDailyPosting();
assertNotNull(savingsProductID); assertNotNull(savingsProductID);
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplicationOnDate(clientID, savingsProductID, return savingsAccountHelper.createApproveActivateSavingsAccount(clientID, savingsProductID, startDate);
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;
} }
private Integer createSavingsProductDailyPosting() { private Integer createSavingsProductDailyPosting() {
@@ -382,28 +374,31 @@ public class SavingsAccountTransactionTest {
final List<BatchResponse> responses = enclosingTransaction final List<BatchResponse> responses = enclosingTransaction
? BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, json) ? BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, json)
: BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec, responseSpec, json); : BatchHelper.postBatchRequestsWithoutEnclosingTransaction(requestSpec, responseSpec, json);
assertNotNull(responses); assertNotNull(responses, "Responses");
if (enclosingTransaction) { if (enclosingTransaction) {
Integer statusCode1 = responses.get(0).getStatusCode(); 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); assertTrue(SC_OK == statusCode1 || SC_CONFLICT == statusCode1, "Status code: " + statusCode1);
if (SC_OK == 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(); Integer statusCode4 = responses.get(3).getStatusCode();
assertNotNull(statusCode4); assertNotNull(statusCode4, "Last enlosingTransaction OK response status code");
assertEquals(SC_OK, statusCode4); assertEquals(SC_OK, statusCode4, "Last enlosingTransaction OK response status code");
} else { } else {
assertEquals(1, responses.size()); assertEquals(1, responses.size(), "Response size for enlosingTransaction failed response");
} }
} else { } else {
assertEquals(4, responses.size()); assertEquals(4, responses.size(), "Response size for without-enlosingTransaction response");
Integer statusCode1 = responses.get(0).getStatusCode(); Integer statusCode1 = responses.get(0).getStatusCode();
assertNotNull(statusCode1); assertNotNull(statusCode1, "First without-enlosingTransaction response status code");
assertTrue(SC_OK == statusCode1 || SC_CONFLICT == statusCode1, "Status code: " + statusCode1); assertTrue(SC_OK == statusCode1 || SC_CONFLICT == statusCode1,
"First without-enlosingTransaction response status code: " + statusCode1);
Integer statusCode4 = responses.get(3).getStatusCode(); Integer statusCode4 = responses.get(3).getStatusCode();
assertNotNull(statusCode4); assertNotNull(statusCode4, "Last without-enlosingTransaction response status code");
assertTrue(SC_OK == statusCode1 ? (SC_OK == statusCode4 || SC_CONFLICT == statusCode4) assertTrue(
: (SC_FORBIDDEN == statusCode4 || SC_CONFLICT == statusCode4), "Status code: " + statusCode4); SC_OK == statusCode1 ? (SC_OK == statusCode4 || SC_CONFLICT == statusCode4)
: (SC_FORBIDDEN == statusCode4 || SC_CONFLICT == statusCode4),
"Last without-enlosingTransaction response status code: " + statusCode4);
} }
} else { } else {
String json = transactionData.getJson(); String json = transactionData.getJson();
@@ -417,14 +412,14 @@ public class SavingsAccountTransactionTest {
} }
private static boolean checkConcurrentResponse(String response) { private static boolean checkConcurrentResponse(String response) {
assertNotNull(response); assertNotNull(response, "Single response");
JsonPath res = JsonPath.from(response); JsonPath res = JsonPath.from(response);
String statusCode = res.get("httpStatusCode"); String statusCode = res.get("httpStatusCode");
if (statusCode == null) { if (statusCode == null) {
assertNotNull(res.get(CommonConstants.RESPONSE_RESOURCE_ID)); assertNotNull(res.get(RESPONSE_RESOURCE_ID), "Single response " + RESPONSE_RESOURCE_ID);
return true; return true;
} }
assertEquals(String.valueOf(SC_CONFLICT), statusCode); assertEquals(String.valueOf(SC_CONFLICT), statusCode, "Single response status code");
return false; return false;
} }
} }
@@ -439,22 +434,22 @@ public class SavingsAccountTransactionTest {
RequestSpecification requestSpec = savingsHelper.getRequestSpec(); RequestSpecification requestSpec = savingsHelper.getRequestSpec();
ResponseSpecification responseSpec = savingsHelper.getResponseSpec(); ResponseSpecification responseSpec = savingsHelper.getResponseSpec();
final List<BatchResponse> responses = BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, json); final List<BatchResponse> responses = BatchHelper.postBatchRequestsWithEnclosingTransaction(requestSpec, responseSpec, json);
assertNotNull(responses); assertNotNull(responses, "Responses");
BatchResponse response1 = responses.get(0); BatchResponse response1 = responses.get(0);
Integer statusCode = response1.getStatusCode(); Integer statusCode = response1.getStatusCode();
String msg = Strings.nullToEmpty(response1.getBody()); String msg = Strings.nullToEmpty(response1.getBody());
assertNotNull(statusCode); assertNotNull(statusCode, "First response status code");
assertTrue( assertTrue(
SC_OK == statusCode || SC_CONFLICT == statusCode SC_OK == statusCode || SC_CONFLICT == statusCode
|| (SC_FORBIDDEN == statusCode && msg.contains("Cannot add or update a child row")), || (SC_FORBIDDEN == statusCode && msg.contains("Cannot add or update a child row")),
"Status code: " + statusCode + ", message: " + msg); "Status code: " + statusCode + ", message: " + msg);
if (SC_OK == statusCode) { if (SC_OK == statusCode) {
assertEquals(4, responses.size()); assertEquals(4, responses.size(), "Response size for OK response");
Integer statusCode4 = responses.get(3).getStatusCode(); Integer statusCode4 = responses.get(3).getStatusCode();
assertNotNull(statusCode4); assertNotNull(statusCode4, "Last OK response status code");
assertEquals(SC_OK, statusCode4); assertEquals(SC_OK, statusCode4, "Last OK response status code");
} else { } 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 static org.junit.jupiter.api.Assertions.assertEquals;
import com.google.gson.Gson;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification; import io.restassured.specification.ResponseSpecification;
import java.util.Collections; import java.util.Collections;
@@ -27,6 +28,7 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import org.apache.fineract.client.util.JSON;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -38,13 +40,15 @@ import org.slf4j.LoggerFactory;
public class AuditHelper { public class AuditHelper {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private static final Logger LOG = LoggerFactory.getLogger(AuditHelper.class); 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 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 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) { public AuditHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
this.requestSpec = requestSpec; this.requestSpec = requestSpec;
this.responseSpec = responseSpec; this.responseSpec = responseSpec;
@@ -119,8 +119,8 @@ public class GlobalConfigurationHelper {
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations(); ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec); ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec);
Assertions.assertEquals(52, expectedGlobalConfigurations.size()); Assertions.assertEquals(53, expectedGlobalConfigurations.size());
Assertions.assertEquals(52, actualGlobalConfigurations.size()); Assertions.assertEquals(53, actualGlobalConfigurations.size());
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) { for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
@@ -571,9 +571,16 @@ public class GlobalConfigurationHelper {
assetExternalizationOfNonActiveLoans.put("value", 0); assetExternalizationOfNonActiveLoans.put("value", 0);
assetExternalizationOfNonActiveLoans.put("enabled", true); assetExternalizationOfNonActiveLoans.put("enabled", true);
assetExternalizationOfNonActiveLoans.put("trapDoor", false); assetExternalizationOfNonActiveLoans.put("trapDoor", false);
assetExternalizationOfNonActiveLoans.put("string_value", "due-date");
defaults.add(assetExternalizationOfNonActiveLoans); 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; return defaults;
} }
@@ -23,8 +23,9 @@ import com.linecorp.armeria.internal.shaded.guava.reflect.TypeToken;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification; import io.restassured.specification.ResponseSpecification;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.HashMap;
import org.apache.fineract.client.models.GetMakerCheckerResponse; import java.util.List;
import java.util.Map;
import org.apache.fineract.client.util.JSON; import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.common.Utils; 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 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 RequestSpecification requestSpec;
private final ResponseSpecification responseSpec; private final ResponseSpecification responseSpec;
@@ -42,10 +43,25 @@ public class MakercheckersHelper {
this.responseSpec = responseSpec; this.responseSpec = responseSpec;
} }
public ArrayList<GetMakerCheckerResponse> getMakerCheckerList() { public List<Map<String, Object>> getMakerCheckerList(Map<String, String> queryParams) {
final String response = Utils.performServerGet(this.requestSpec, this.responseSpec, MAKERCHECKER_URL); StringBuilder url = new StringBuilder(MAKERCHECKER_URL).append("?").append(Utils.TENANT_IDENTIFIER);
Type makerCheckerList = new TypeToken<ArrayList<GetMakerCheckerResponse>>() {}.getType(); 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); 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.DEFAULT_TENANT;
import static org.apache.fineract.integrationtests.common.Utils.TENANT_PARAM_NAME; 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.common.reflect.TypeToken;
import com.google.gson.Gson; import com.google.gson.Gson;
@@ -174,6 +175,16 @@ public class SavingsAccountHelper extends IntegrationTest {
savingsApplicationJSON, responseAttribute); 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, public HashMap updateSavingsAccount(final Integer id, final Integer savingsProductID, final Integer savingsId,
final String accountType) { final String accountType) {
final String savingsApplicationJSON = new SavingsApplicationTestBuilder() // final String savingsApplicationJSON = new SavingsApplicationTestBuilder() //
@@ -19,24 +19,36 @@
package org.apache.fineract.integrationtests.useradministration.roles; package org.apache.fineract.integrationtests.useradministration.roles;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.restassured.specification.RequestSpecification; import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification; import io.restassured.specification.ResponseSpecification;
import java.lang.reflect.Type;
import java.util.HashMap; 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.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 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 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 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 DISABLE_ROLE_COMMAND = "disable";
private static final String ENABLE_ROLE_COMMAND = "enable"; 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) { public static Integer createRole(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
return Utils.performServerPost(requestSpec, responseSpec, CREATE_ROLE_URL, getTestCreateRoleAsJSON(), "resourceId"); 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, 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, return Utils.performServerPut(requestSpec, responseSpec, ROLE_URL + "/" + roleId + "/permissions?" + Utils.TENANT_IDENTIFIER,
getAddPermissionsToRoleJSON(permissionMap)); getAddPermissionsToRoleJSON(permissionMap));
} }
private static String getAddPermissionsToRoleJSON(HashMap<String, Boolean> permissionMap) { public static List<PermissionData> getPermissions(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final HashMap<String, HashMap<String, Boolean>> map = new HashMap<>(); 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); map.put("permissions", permissionMap);
return new Gson().toJson(map); return new Gson().toJson(map);
} }
@@ -84,5 +108,4 @@ public final class RolesHelper {
private static String createRoleOperationURL(final String command, final Integer roleId) { private static String createRoleOperationURL(final String command, final Integer roleId) {
return ROLE_URL + "/" + roleId + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER; return ROLE_URL + "/" + roleId + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER;
} }
} }