FINERACT-1724
- Error handler is springified, exception mappers are retrieved as spring beans - backward compatibility is maintained
This commit is contained in:
@@ -18,29 +18,21 @@
|
||||
*/
|
||||
package org.apache.fineract.batch.exception;
|
||||
|
||||
import static org.springframework.core.ResolvableType.forClassWithGenerics;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
|
||||
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformApiDataValidationExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDataIntegrityExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDomainRuleExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformInternalServerExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformResourceNotFoundExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.UnsupportedParameterExceptionMapper;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.SetUtils;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.DefaultExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Provides an Error Handler method that returns an object of type {@link ErrorInfo} to the CommandStrategy which raised
|
||||
@@ -50,60 +42,41 @@ import org.springframework.transaction.TransactionException;
|
||||
* @author Rishabh Shukla
|
||||
* @see org.apache.fineract.batch.command.CommandStrategy
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public final class ErrorHandler {
|
||||
|
||||
@Autowired
|
||||
private final ApplicationContext ctx;
|
||||
|
||||
@Autowired
|
||||
private final DefaultExceptionMapper defaultExceptionMapper;
|
||||
|
||||
private static final Gson JSON_HELPER = GoogleGsonSerializerHelper.createGsonBuilder(true).create();
|
||||
|
||||
private static final Map<Class<? extends Exception>, Function<RuntimeException, ErrorInfo>> EXCEPTION_HANDLERS = Collections
|
||||
.synchronizedMap(new LinkedHashMap<>());
|
||||
private static final Map.Entry<Class<? extends Exception>, Function<RuntimeException, ErrorInfo>> DEFAULT_ERROR_HANDLER = Map.entry(
|
||||
RuntimeException.class,
|
||||
runtimeException -> new ErrorInfo(500, 9999, "{\"Exception\": %s}".formatted(runtimeException.getMessage())));
|
||||
|
||||
static {
|
||||
EXCEPTION_HANDLERS.put(AbstractPlatformResourceNotFoundException.class,
|
||||
runtimeException -> handleException(runtimeException, new PlatformResourceNotFoundExceptionMapper(), 1001));
|
||||
EXCEPTION_HANDLERS.put(UnsupportedParameterException.class,
|
||||
runtimeException -> handleException(runtimeException, new UnsupportedParameterExceptionMapper(), 2001));
|
||||
EXCEPTION_HANDLERS.put(PlatformApiDataValidationException.class,
|
||||
runtimeException -> handleException(runtimeException, new PlatformApiDataValidationExceptionMapper(), 2002));
|
||||
EXCEPTION_HANDLERS.put(PlatformDataIntegrityException.class,
|
||||
runtimeException -> handleException(runtimeException, new PlatformDataIntegrityExceptionMapper(), 3001));
|
||||
EXCEPTION_HANDLERS.put(AbstractPlatformDomainRuleException.class,
|
||||
runtimeException -> handleException(runtimeException, new PlatformDomainRuleExceptionMapper(), 9999));
|
||||
EXCEPTION_HANDLERS.put(TransactionException.class, runtimeException -> new ErrorInfo(HttpStatus.SC_INTERNAL_SERVER_ERROR, 4001,
|
||||
"{\"Exception\": %s}".formatted(runtimeException.getMessage())));
|
||||
EXCEPTION_HANDLERS.put(PlatformInternalServerException.class,
|
||||
runtimeException -> handleException(runtimeException, new PlatformInternalServerExceptionMapper(), 5001));
|
||||
EXCEPTION_HANDLERS.put(NonTransientDataAccessException.class, runtimeException -> new ErrorInfo(HttpStatus.SC_INTERNAL_SERVER_ERROR,
|
||||
4002, "{\"Exception\": %s}".formatted(runtimeException.getMessage())));
|
||||
EXCEPTION_HANDLERS.put(DEFAULT_ERROR_HANDLER.getKey(), DEFAULT_ERROR_HANDLER.getValue());
|
||||
private <T extends RuntimeException> ExceptionMapper<T> findMostSpecificExceptionHandler(T exception) {
|
||||
Class<?> clazz = exception.getClass();
|
||||
do {
|
||||
Set<String> exceptionMappers = createSet(ctx.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class, clazz)));
|
||||
Set<String> fineractErrorMappers = createSet(ctx.getBeanNamesForType(FineractExceptionMapper.class));
|
||||
SetUtils.SetView<String> intersection = SetUtils.intersection(exceptionMappers, fineractErrorMappers);
|
||||
if (intersection.size() > 0) {
|
||||
// noinspection unchecked
|
||||
return (ExceptionMapper<T>) ctx.getBean(intersection.iterator().next());
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
} while (!clazz.equals(Exception.class));
|
||||
// noinspection unchecked
|
||||
return (ExceptionMapper<T>) defaultExceptionMapper;
|
||||
}
|
||||
|
||||
private ErrorHandler() {}
|
||||
|
||||
public static void registerNewErrorHandler(final Class<? extends RuntimeException> exceptionClass, final ExceptionMapper mapper,
|
||||
int errorCode) {
|
||||
LinkedHashMap<Class<? extends Exception>, Function<RuntimeException, ErrorInfo>> newHandlers = new LinkedHashMap<>();
|
||||
newHandlers.put(exceptionClass, runtimeException -> handleException(runtimeException, mapper, errorCode));
|
||||
EXCEPTION_HANDLERS.forEach(newHandlers::putIfAbsent);
|
||||
EXCEPTION_HANDLERS.clear();
|
||||
newHandlers.forEach(EXCEPTION_HANDLERS::putIfAbsent);
|
||||
}
|
||||
|
||||
public static void registerNewErrorHandler(final Class<? extends RuntimeException> exceptionClass,
|
||||
Function<RuntimeException, ErrorInfo> function) {
|
||||
LinkedHashMap<Class<? extends Exception>, Function<RuntimeException, ErrorInfo>> newHandlers = new LinkedHashMap<>();
|
||||
newHandlers.put(exceptionClass, function);
|
||||
EXCEPTION_HANDLERS.forEach(newHandlers::putIfAbsent);
|
||||
EXCEPTION_HANDLERS.clear();
|
||||
newHandlers.forEach(EXCEPTION_HANDLERS::putIfAbsent);
|
||||
}
|
||||
|
||||
private static ErrorInfo handleException(final RuntimeException exception, final ExceptionMapper mapper, final int errorCode) {
|
||||
final Response response = mapper.toResponse(exception);
|
||||
final String errorBody = JSON_HELPER.toJson(response.getEntity());
|
||||
return new ErrorInfo(response.getStatus(), errorCode, errorBody);
|
||||
private <T> Set<T> createSet(T[] array) {
|
||||
if (array == null) {
|
||||
return Set.of();
|
||||
} else {
|
||||
return Set.of(array);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,9 +85,10 @@ public final class ErrorHandler {
|
||||
* @param exception
|
||||
* @return ErrorInfo
|
||||
*/
|
||||
public static ErrorInfo handler(final RuntimeException exception) {
|
||||
Map.Entry<Class<? extends Exception>, Function<RuntimeException, ErrorInfo>> errorhandler = EXCEPTION_HANDLERS.entrySet().stream()
|
||||
.filter(e -> e.getKey().isAssignableFrom(exception.getClass())).findFirst().orElse(DEFAULT_ERROR_HANDLER);
|
||||
return errorhandler.getValue().apply(exception);
|
||||
public ErrorInfo handle(final RuntimeException exception) {
|
||||
ExceptionMapper<RuntimeException> exceptionMapper = findMostSpecificExceptionHandler(exception);
|
||||
Response response = exceptionMapper.toResponse(exception);
|
||||
return new ErrorInfo(response.getStatus(), ((FineractExceptionMapper) exceptionMapper).errorCode(),
|
||||
JSON_HELPER.toJson(response.getEntity()));
|
||||
}
|
||||
}
|
||||
|
||||
+7
-6
@@ -82,6 +82,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
private final CommandStrategyProvider strategyProvider;
|
||||
private final ResolutionHelper resolutionHelper;
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
private final List<BatchFilter> batchFilters;
|
||||
|
||||
@@ -106,7 +107,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
final List<BatchRequestNode> batchRequestNodes = this.resolutionHelper.getDependingRequests(requestList);
|
||||
if (batchRequestNodes.isEmpty()) {
|
||||
final BatchResponse response = new BatchResponse();
|
||||
ErrorInfo ex = ErrorHandler.handler(new ClientDetailsNotFoundException());
|
||||
ErrorInfo ex = errorHandler.handle(new ClientDetailsNotFoundException());
|
||||
response.setStatusCode(500);
|
||||
response.setBody(ex.getMessage());
|
||||
responseList.add(response);
|
||||
@@ -156,7 +157,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
try {
|
||||
resolvedChildRequest = this.resolutionHelper.resoluteRequest(childNode.getRequest(), response);
|
||||
} catch (RuntimeException ex) {
|
||||
throw new BatchExecutionException(childNode.getRequest(), ex);
|
||||
throw new BatchExecutionException(childNode.getRequest(), ex, errorHandler.handle(ex));
|
||||
}
|
||||
callRequestRecursive(resolvedChildRequest, childNode, responseList, uriInfo, enclosingTransaction);
|
||||
|
||||
@@ -206,7 +207,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
log.debug("Batch request: method [{}], relative url [{}]", request.getMethod(), request.getRelativeUrl());
|
||||
Either<RuntimeException, BatchRequest> preprocessorResult = runPreprocessors(request);
|
||||
if (preprocessorResult.isLeft()) {
|
||||
throw new BatchExecutionException(request, preprocessorResult.getLeft());
|
||||
throw new BatchExecutionException(request, preprocessorResult.getLeft(), errorHandler.handle(preprocessorResult.getLeft()));
|
||||
} else {
|
||||
request = preprocessorResult.get();
|
||||
}
|
||||
@@ -231,7 +232,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
} catch (AbstractIdempotentCommandException idempotentException) {
|
||||
return handleIdempotentRequests(idempotentException, request);
|
||||
} catch (RuntimeException ex) {
|
||||
throw new BatchExecutionException(request, ex);
|
||||
throw new BatchExecutionException(request, ex, errorHandler.handle(ex));
|
||||
} finally {
|
||||
BatchRequestContextHolder.resetRequestAttributes();
|
||||
}
|
||||
@@ -366,7 +367,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
return Arrays.asList(errResponse);
|
||||
} catch (RuntimeException ex) {
|
||||
status.setRollbackOnly();
|
||||
ErrorInfo e = ErrorHandler.handler(ex);
|
||||
ErrorInfo e = errorHandler.handle(ex);
|
||||
BatchResponse errResponse = new BatchResponse();
|
||||
errResponse.setStatusCode(e.getStatusCode());
|
||||
errResponse.setBody(e.getMessage());
|
||||
@@ -374,7 +375,7 @@ public class BatchApiServiceImpl implements BatchApiService {
|
||||
}
|
||||
});
|
||||
} catch (TransactionException | NonTransientDataAccessException ex) {
|
||||
ErrorInfo e = ErrorHandler.handler(ex);
|
||||
ErrorInfo e = errorHandler.handle(ex);
|
||||
return createErrorResponse(responseList, e.getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -20,7 +20,6 @@ package org.apache.fineract.batch.service;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.batch.domain.BatchRequest;
|
||||
import org.apache.fineract.batch.exception.ErrorHandler;
|
||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||
|
||||
@Getter
|
||||
@@ -29,9 +28,9 @@ public class BatchExecutionException extends RuntimeException {
|
||||
private final BatchRequest request;
|
||||
private final ErrorInfo errorInfo;
|
||||
|
||||
public BatchExecutionException(BatchRequest request, RuntimeException ex) {
|
||||
public BatchExecutionException(BatchRequest request, RuntimeException ex, ErrorInfo errorInfo) {
|
||||
super("Error executing batch request: " + request, ex);
|
||||
this.request = request;
|
||||
this.errorInfo = ErrorHandler.handler(ex);
|
||||
this.errorInfo = errorInfo;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -46,6 +46,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
public class CommandSourceService {
|
||||
|
||||
private final CommandSourceRepository commandSourceRepository;
|
||||
private final ErrorHandler errorHandler;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
||||
public CommandSource saveInitial(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||
@@ -83,7 +84,7 @@ public class CommandSourceService {
|
||||
|
||||
public ErrorInfo generateErrorException(Throwable t) {
|
||||
if (t instanceof final RuntimeException e) {
|
||||
return ErrorHandler.handler(e);
|
||||
return errorHandler.handle(e);
|
||||
} else {
|
||||
return new ErrorInfo(500, 9999, "{\"Exception\": " + t.toString() + "}");
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class DefaultExceptionMapper implements FineractExceptionMapper, ExceptionMapper<RuntimeException> {
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 9999;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response toResponse(RuntimeException runtimeException) {
|
||||
return Response.status(SC_INTERNAL_SERVER_ERROR).entity(Map.of("Exception", runtimeException.getMessage()))
|
||||
.type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public interface FineractExceptionMapper {
|
||||
|
||||
int errorCode();
|
||||
}
|
||||
+7
-1
@@ -40,7 +40,8 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class PlatformApiDataValidationExceptionMapper implements ExceptionMapper<PlatformApiDataValidationException> {
|
||||
public class PlatformApiDataValidationExceptionMapper
|
||||
implements FineractExceptionMapper, ExceptionMapper<PlatformApiDataValidationException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final PlatformApiDataValidationException exception) {
|
||||
@@ -50,4 +51,9 @@ public class PlatformApiDataValidationExceptionMapper implements ExceptionMapper
|
||||
|
||||
return Response.status(Status.BAD_REQUEST).entity(dataValidationErrorResponse).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 2002;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -40,7 +40,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class PlatformDataIntegrityExceptionMapper implements ExceptionMapper<PlatformDataIntegrityException> {
|
||||
public class PlatformDataIntegrityExceptionMapper implements FineractExceptionMapper, ExceptionMapper<PlatformDataIntegrityException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final PlatformDataIntegrityException exception) {
|
||||
@@ -50,4 +50,9 @@ public class PlatformDataIntegrityExceptionMapper implements ExceptionMapper<Pla
|
||||
|
||||
return Response.status(Status.FORBIDDEN).entity(dataIntegrityError).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 3001;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -40,7 +40,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class PlatformDomainRuleExceptionMapper implements ExceptionMapper<AbstractPlatformDomainRuleException> {
|
||||
public class PlatformDomainRuleExceptionMapper implements FineractExceptionMapper, ExceptionMapper<AbstractPlatformDomainRuleException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final AbstractPlatformDomainRuleException exception) {
|
||||
@@ -51,4 +51,9 @@ public class PlatformDomainRuleExceptionMapper implements ExceptionMapper<Abstra
|
||||
// domain/business logic
|
||||
return Response.status(Status.FORBIDDEN).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 9999;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -39,7 +39,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class PlatformInternalServerExceptionMapper implements ExceptionMapper<PlatformInternalServerException> {
|
||||
public class PlatformInternalServerExceptionMapper implements FineractExceptionMapper, ExceptionMapper<PlatformInternalServerException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final PlatformInternalServerException exception) {
|
||||
@@ -48,4 +48,9 @@ public class PlatformInternalServerExceptionMapper implements ExceptionMapper<Pl
|
||||
exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
|
||||
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 5001;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -40,7 +40,8 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class PlatformResourceNotFoundExceptionMapper implements ExceptionMapper<AbstractPlatformResourceNotFoundException> {
|
||||
public class PlatformResourceNotFoundExceptionMapper
|
||||
implements FineractExceptionMapper, ExceptionMapper<AbstractPlatformResourceNotFoundException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final AbstractPlatformResourceNotFoundException exception) {
|
||||
@@ -49,4 +50,9 @@ public class PlatformResourceNotFoundExceptionMapper implements ExceptionMapper<
|
||||
exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
|
||||
return Response.status(Status.NOT_FOUND).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 1001;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -40,7 +40,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class UnsupportedParameterExceptionMapper implements ExceptionMapper<UnsupportedParameterException> {
|
||||
public class UnsupportedParameterExceptionMapper implements FineractExceptionMapper, ExceptionMapper<UnsupportedParameterException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(final UnsupportedParameterException exception) {
|
||||
@@ -62,4 +62,9 @@ public class UnsupportedParameterExceptionMapper implements ExceptionMapper<Unsu
|
||||
|
||||
return Response.status(Status.BAD_REQUEST).entity(invalidParameterError).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 2001;
|
||||
}
|
||||
}
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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.batch.exception;
|
||||
|
||||
import static org.springframework.core.ResolvableType.forClassWithGenerics;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import java.util.InputMismatchException;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.DefaultExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ErrorHandlerTest {
|
||||
|
||||
@Mock
|
||||
DefaultExceptionMapper defaultExceptionMapper;
|
||||
|
||||
@Mock
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
@Mock(extraInterfaces = FineractExceptionMapper.class)
|
||||
ExceptionMapper<NullPointerException> exceptionMapper;
|
||||
|
||||
@Mock(extraInterfaces = FineractExceptionMapper.class)
|
||||
ExceptionMapper<InputMismatchException> inputMismatchMapper;
|
||||
|
||||
@Mock
|
||||
Response response;
|
||||
|
||||
@InjectMocks
|
||||
ErrorHandler errorHandler;
|
||||
|
||||
@Test
|
||||
public void testErrorHandlerFound() {
|
||||
// given
|
||||
NullPointerException npe = new NullPointerException("Div by zero");
|
||||
Mockito.when(applicationContext.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class, NullPointerException.class)))
|
||||
.thenReturn(new String[] { "exceptionMapper1", "exceptionMapper2", "exceptionMapper3" });
|
||||
Mockito.when(applicationContext.getBeanNamesForType(FineractExceptionMapper.class))
|
||||
.thenReturn(new String[] { "exceptionMapper2", "exceptionMapper4" });
|
||||
Mockito.when(applicationContext.getBean("exceptionMapper2")).thenReturn(exceptionMapper);
|
||||
Mockito.when(exceptionMapper.toResponse(npe)).thenReturn(response);
|
||||
Mockito.when(response.getStatus()).thenReturn(406);
|
||||
Mockito.when(response.getEntity()).thenReturn(Map.of("Exception", "message body"));
|
||||
Mockito.when(((FineractExceptionMapper) exceptionMapper).errorCode()).thenReturn(1234);
|
||||
|
||||
// when
|
||||
ErrorInfo errorInfo = errorHandler.handle(npe);
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(1234, errorInfo.getErrorCode());
|
||||
Assertions.assertEquals("{\n \"Exception\": \"message body\"\n}", errorInfo.getMessage());
|
||||
Assertions.assertEquals(406, errorInfo.getStatusCode());
|
||||
Mockito.verifyNoMoreInteractions(exceptionMapper);
|
||||
Mockito.verifyNoInteractions(defaultExceptionMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorHandlerChecksParentException() {
|
||||
// given
|
||||
InputMismatchException ime = new InputMismatchException("Input Mismatch");
|
||||
Mockito.when(applicationContext.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class, InputMismatchException.class)))
|
||||
.thenReturn(new String[] {}); // no direct handler for InputMismatchException
|
||||
Mockito.when(applicationContext.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class, NoSuchElementException.class)))
|
||||
.thenReturn(new String[] { "inputMismatchMapper" });
|
||||
Mockito.when(applicationContext.getBeanNamesForType(FineractExceptionMapper.class))
|
||||
.thenReturn(new String[] { "inputMismatchMapper", "someOtherMapper", "yetAnotherMapper" });
|
||||
Mockito.when(applicationContext.getBean("inputMismatchMapper")).thenReturn(inputMismatchMapper);
|
||||
Mockito.when(inputMismatchMapper.toResponse(ime)).thenReturn(response);
|
||||
Mockito.when(response.getStatus()).thenReturn(406);
|
||||
Mockito.when(response.getEntity()).thenReturn(Map.of("Exception", "message body"));
|
||||
Mockito.when(((FineractExceptionMapper) inputMismatchMapper).errorCode()).thenReturn(1234);
|
||||
|
||||
// when
|
||||
ErrorInfo errorInfo = errorHandler.handle(ime);
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(1234, errorInfo.getErrorCode());
|
||||
Assertions.assertEquals("{\n \"Exception\": \"message body\"\n}", errorInfo.getMessage());
|
||||
Assertions.assertEquals(406, errorInfo.getStatusCode());
|
||||
Mockito.verifyNoMoreInteractions(inputMismatchMapper);
|
||||
Mockito.verifyNoInteractions(exceptionMapper);
|
||||
Mockito.verifyNoInteractions(defaultExceptionMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorHandlerFallsBackToDefault() {
|
||||
// given
|
||||
NullPointerException npe = new NullPointerException("Div by zero");
|
||||
Mockito.when(applicationContext.getBeanNamesForType(forClassWithGenerics(ExceptionMapper.class, NullPointerException.class)))
|
||||
.thenReturn(new String[] {});
|
||||
Mockito.when(applicationContext.getBeanNamesForType(FineractExceptionMapper.class))
|
||||
.thenReturn(new String[] { "exceptionMapper2", "exceptionMapper4" });
|
||||
Mockito.when(defaultExceptionMapper.toResponse(npe)).thenReturn(response);
|
||||
Mockito.when(response.getStatus()).thenReturn(406);
|
||||
Mockito.when(response.getEntity()).thenReturn(Map.of("Exception", "message body"));
|
||||
Mockito.when(((FineractExceptionMapper) defaultExceptionMapper).errorCode()).thenReturn(1234);
|
||||
|
||||
// when
|
||||
ErrorInfo errorInfo = errorHandler.handle(npe);
|
||||
|
||||
// then
|
||||
Assertions.assertEquals(1234, errorInfo.getErrorCode());
|
||||
Assertions.assertEquals("{\n \"Exception\": \"message body\"\n}", errorInfo.getMessage());
|
||||
Assertions.assertEquals(406, errorInfo.getStatusCode());
|
||||
Mockito.verifyNoInteractions(exceptionMapper);
|
||||
Mockito.verifyNoMoreInteractions(defaultExceptionMapper);
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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 static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class DefaultExceptionMapperTest {
|
||||
|
||||
@Test
|
||||
public void testDefaultExceptionMapper() {
|
||||
DefaultExceptionMapper defaultExceptionMapper = new DefaultExceptionMapper();
|
||||
Response response = defaultExceptionMapper.toResponse(new RuntimeException("error happened"));
|
||||
Assertions.assertEquals(9999, defaultExceptionMapper.errorCode());
|
||||
|
||||
Assertions.assertEquals(Map.of("Exception", "error happened"), response.getEntity());
|
||||
Assertions.assertEquals(SC_INTERNAL_SERVER_ERROR, response.getStatus());
|
||||
Assertions.assertEquals(MediaType.APPLICATION_JSON, response.getMediaType().toString());
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
|
||||
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class LinkedAccountRequiredExceptionMapper implements FineractExceptionMapper, ExceptionMapper<LinkedAccountRequiredException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(LinkedAccountRequiredException exception) {
|
||||
log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage());
|
||||
final ApiGlobalErrorResponse notFoundErrorResponse = ApiGlobalErrorResponse.domainRuleViolation(
|
||||
exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
|
||||
// request understood but not carried out due to it violating some
|
||||
// domain/business logic
|
||||
return Response.status(Response.Status.FORBIDDEN).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 3002;
|
||||
}
|
||||
|
||||
}
|
||||
+19
-14
@@ -18,26 +18,31 @@
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.core.exception;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.apache.fineract.batch.exception.ErrorHandler;
|
||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDomainRuleExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
||||
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ErrorHandlerRegister {
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class LoanIdsHardLockedExceptionMapper implements FineractExceptionMapper, ExceptionMapper<LoanIdsHardLockedException> {
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
ErrorHandler.registerNewErrorHandler(LoanIdsHardLockedException.class, runtimeException -> new ErrorInfo(HttpStatus.SC_CONFLICT,
|
||||
4090,
|
||||
ApiGlobalErrorResponse.loanIsLocked(((LoanIdsHardLockedException) runtimeException).getLoanIdFromRequest()).toJson()));
|
||||
ErrorHandler.registerNewErrorHandler(LinkedAccountRequiredException.class, new PlatformDomainRuleExceptionMapper(), 3002);
|
||||
ErrorHandler.registerNewErrorHandler(MultiDisbursementDataRequiredException.class, new PlatformDomainRuleExceptionMapper(), 3003);
|
||||
@Override
|
||||
public Response toResponse(LoanIdsHardLockedException exception) {
|
||||
return Response.status(HttpStatus.SC_CONFLICT)
|
||||
.entity(ApiGlobalErrorResponse.loanIsLocked(exception.getLoanIdFromRequest()).toJson()).type(MediaType.APPLICATION_JSON)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 4090;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
||||
import org.apache.fineract.infrastructure.core.exceptionmapper.FineractExceptionMapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Scope("singleton")
|
||||
@Slf4j
|
||||
public class MultiDisbursementDataRequiredExceptionMapper
|
||||
implements FineractExceptionMapper, ExceptionMapper<MultiDisbursementDataRequiredException> {
|
||||
|
||||
@Override
|
||||
public Response toResponse(MultiDisbursementDataRequiredException exception) {
|
||||
log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage());
|
||||
final ApiGlobalErrorResponse notFoundErrorResponse = ApiGlobalErrorResponse.domainRuleViolation(
|
||||
exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage(), exception.getDefaultUserMessageArgs());
|
||||
// request understood but not carried out due to it violating some
|
||||
// domain/business logic
|
||||
return Response.status(Response.Status.FORBIDDEN).entity(notFoundErrorResponse).type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int errorCode() {
|
||||
return 3003;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import static org.apache.http.HttpStatus.SC_FORBIDDEN;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
||||
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LinkedAccountRequiredExceptionMapperTest {
|
||||
|
||||
@Test
|
||||
public void testExceptionMapper() {
|
||||
LinkedAccountRequiredExceptionMapper exceptionMapper = new LinkedAccountRequiredExceptionMapper();
|
||||
LinkedAccountRequiredException exception = new LinkedAccountRequiredException("entity", "message", "args");
|
||||
Response response = exceptionMapper.toResponse(exception);
|
||||
Assertions.assertEquals(3002, exceptionMapper.errorCode());
|
||||
|
||||
Assertions.assertInstanceOf(ApiGlobalErrorResponse.class, response.getEntity());
|
||||
Assertions.assertEquals(1, ((ApiGlobalErrorResponse) response.getEntity()).getErrors().size());
|
||||
Assertions.assertEquals("message", ((ApiGlobalErrorResponse) response.getEntity()).getErrors().get(0).getDefaultUserMessage());
|
||||
Assertions.assertEquals(SC_FORBIDDEN, response.getStatus());
|
||||
Assertions.assertEquals(MediaType.APPLICATION_JSON, response.getMediaType().toString());
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -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.infrastructure.core.exception;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LoanIdsHardLockedExceptionMapperTest {
|
||||
|
||||
@Test
|
||||
public void testExceptionMapper() {
|
||||
LoanIdsHardLockedExceptionMapper exceptionMapper = new LoanIdsHardLockedExceptionMapper();
|
||||
Response response = exceptionMapper.toResponse(new LoanIdsHardLockedException(123L));
|
||||
Assertions.assertEquals(4090, exceptionMapper.errorCode());
|
||||
|
||||
Assertions.assertEquals("{\"developerMessage\":\"Loan is locked by the COB job. Loan ID: 123\","
|
||||
+ "\"httpStatusCode\":\"Conflict\"," + "\"defaultUserMessage\":\"Loan is locked by the COB job. Loan ID: \\\" + loanId\","
|
||||
+ "\"userMessageGlobalisationCode\":\"error.msg.loan.locked\"," + "\"errors\":[]}", response.getEntity());
|
||||
Assertions.assertEquals(409, response.getStatus());
|
||||
Assertions.assertEquals(MediaType.APPLICATION_JSON, response.getMediaType().toString());
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.exception;
|
||||
|
||||
import static org.apache.http.HttpStatus.SC_FORBIDDEN;
|
||||
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class MultiDisbursementDataRequiredExceptionMapperTest {
|
||||
|
||||
@Test
|
||||
public void testExceptionMapper() {
|
||||
MultiDisbursementDataRequiredExceptionMapper exceptionMapper = new MultiDisbursementDataRequiredExceptionMapper();
|
||||
MultiDisbursementDataRequiredException exception = new MultiDisbursementDataRequiredException("entity", "message", "args");
|
||||
Response response = exceptionMapper.toResponse(exception);
|
||||
Assertions.assertEquals(3003, exceptionMapper.errorCode());
|
||||
|
||||
Assertions.assertInstanceOf(ApiGlobalErrorResponse.class, response.getEntity());
|
||||
Assertions.assertEquals(1, ((ApiGlobalErrorResponse) response.getEntity()).getErrors().size());
|
||||
Assertions.assertEquals("message", ((ApiGlobalErrorResponse) response.getEntity()).getErrors().get(0).getDefaultUserMessage());
|
||||
Assertions.assertEquals(SC_FORBIDDEN, response.getStatus());
|
||||
Assertions.assertEquals(MediaType.APPLICATION_JSON, response.getMediaType().toString());
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -19,9 +19,11 @@
|
||||
package org.apache.fineract.commands.service;
|
||||
|
||||
import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Optional;
|
||||
import org.apache.fineract.batch.exception.ErrorHandler;
|
||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||
import org.apache.fineract.commands.domain.CommandSource;
|
||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||
@@ -46,6 +48,9 @@ public class CommandSourceServiceTest {
|
||||
@Mock
|
||||
private CommandSourceRepository commandSourceRepository;
|
||||
|
||||
@Mock
|
||||
private ErrorHandler errorHandler;
|
||||
|
||||
@InjectMocks
|
||||
private CommandSourceService underTest;
|
||||
|
||||
@@ -103,6 +108,8 @@ public class CommandSourceServiceTest {
|
||||
|
||||
@Test
|
||||
public void testGenerateErrorException() {
|
||||
Mockito.when(errorHandler.handle(any(CodeNotFoundException.class)))
|
||||
.thenReturn(new ErrorInfo(404, 1001, "Code with name `foo` does not exist"));
|
||||
ErrorInfo result = underTest.generateErrorException(new CodeNotFoundException("foo"));
|
||||
Assertions.assertEquals(404, result.getStatusCode());
|
||||
Assertions.assertEquals(1001, result.getErrorCode());
|
||||
|
||||
Reference in New Issue
Block a user