FINERACT-1724

- Error handler is springified, exception mappers are retrieved as spring beans
 - backward compatibility is maintained
This commit is contained in:
Peter Bagrij
2023-07-10 16:56:12 +02:00
committed by Adam Saghy
parent 749a77efac
commit 2afd9d35e7
21 changed files with 604 additions and 101 deletions
@@ -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);
}
}
@@ -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());
}
}