#RestControllerAdvice not working in Spring Boot reactive java application - java

I am currently throwing a custom Exception - RequestValidationException.
ExceptionHandler class:
#RestControllerAdvice
#Slf4j
public class RestExceptionHandler {
#ExceptionHandler(value = RequestValidationException.class)
#ResponseStatus(HttpStatus.PRECONDITION_FAILED)
public Mono<HttpValidationError> handleRequestValidationException(RequestValidationException exception) {
log.error("Received exception: ", exception);
List<String> loc = new ArrayList<>();
loc.add(exception.getMessage());
ValidationError validationError = ValidationError.builder()
.loc(loc)
.msg(exception.getMessage())
.build();
List<ValidationError> errorMessages = new ArrayList<>();
errorMessages.add(validationError);
return Mono.just(HttpValidationError.builder().detail(errorMessages).build());
}
RequestValidationException class:
public class RequestValidationException extends RuntimeException {
public static final HttpStatus statusCode = HttpStatus.PRECONDITION_FAILED;
public RequestValidationException(String text) {
super(text);
}
public HttpStatus getStatusCode() {
return statusCode;
}
}
When the exception is thrown, I want the following response:
Code: 412
{
"detail": [
{
"loc": [
"No ID found to update. Please add an ID"
],
"msg": "No ID found to update. Please add an ID",
"type": null
}
]
}
What I am receiving is:
{
"error_code": 500,
"message": "No ID found to update. Please add an ID"
}
I checked the application logs and nowhere is the RestExceptionHandler being called. It just logs this error:
"level":"ERROR","logger":"c.a.c.c.c.AbstractController","thread":"boundedElastic-1","message":"Controller exception","stack":"<#384d845f> c.a.c.a.e.RequestValidationException
I just can't seem to figure out what's wrong with this code. Can someone point out what I might be missing? Thanks.

I was only able to get this to work with an implementation of AbstractErrorWebExceptionHandler as follows (sorry for the kotlin code):
#Component
#Order(-2)
class GlobalExceptionHandler(errorAttributes: ErrorAttributes,
resources: WebProperties.Resources,
applicationContext: ApplicationContext,
serverCodecConfigurer: ServerCodecConfigurer) : AbstractErrorWebExceptionHandler(errorAttributes, resources, applicationContext) {
companion object {
private val logger = KotlinLogging.logger {}
private const val HTTP_STATUS_KEY = "status"
private const val MESSAGE_KEY = "message"
private const val ERRORS_KEY = "errors"
}
init {
setMessageWriters(serverCodecConfigurer.writers)
}
override fun setMessageWriters(messageWriters: MutableList<HttpMessageWriter<*>>?) {
super.setMessageWriters(messageWriters)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes?): RouterFunction<ServerResponse> {
return RouterFunctions.route({ true }) { request ->
val error: Throwable = getError(request)
logger.error("Handling: ", error)
val errorProperties = getErrorAttributes(request, ErrorAttributeOptions.defaults())
when (error) {
is WebExchangeBindException -> {
....
}
else -> {
...
}
}
ServerResponse.status(HttpStatus.valueOf(errorProperties[HTTP_STATUS_KEY] as Int))
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(errorProperties)
}
}
}
In Java it would be something like:
#Component
#Order(-2)
public class GlobalExceptionHandler extends AbstractErrorWebExceptionHandler {
private static final String HTTP_STATUS_KEY = "status";
private static final String MESSAGE_KEY = "message";
private static final String ERRORS_KEY = "errors";
public GlobalExceptionHandler(ErrorAttributes errorAttributes, Resources resources, ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) {
super(errorAttributes, resources, applicationContext);
this.setMessageWriters(serverCodecConfigurer.getWriters());
}
public final void setMessageWriters(List messageWriters) {
super.setMessageWriters(messageWriters);
}
protected RouterFunction getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> errorPropertiesMap = getErrorAttributes(request,
ErrorAttributeOptions.defaults());
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
}
You can check more details at https://www.baeldung.com/spring-webflux-errors#global.

I made a very trivial mistake of extending the controller with AbstractController class which was causing this issue. Removing it solved my problem.

Related

why does my vue client not receive websocket messages

I want to send messages via queue and topic to my frontend but it doesnt work, useing SOCKJS and STOMP. My backend logs, that new memeber subscribe, but when i send a message via convertAndSend or convertAndSendToUser nothing happens on the client side. pls help me. ty in advance. pls dont delete this question.
SERVER:
CONFIG:
#Override
public void configureMessageBroker(final MessageBrokerRegistry config) {
config.enableSimpleBroker("/user", "/topic", "/queue");
config.setApplicationDestinationPrefixes("/ws");
}
#Override
public void configureClientInboundChannel(final ChannelRegistration registration) {
registration.interceptors(new UserInterceptor());
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/connect").setAllowedOriginPatterns("*").withSockJS();
}
CONTROLLER:
#Controller
#Slf4j
public class WebsocketController {
#Autowired
SimpMessagingTemplate simpMessagingTemplate;
#Autowired
LobbyManagerService lobbyManagerService;
#Autowired
WebsocketService websocketService;
final String newMember = "/queue/newMember/";
final String lobbyDestination = "/topic/lobby/";
#MessageMapping("/get/infos/on/join/{lobby}")
public void getInfosOnJoinLobby(final SimpMessageHeaderAccessor sha, #DestinationVariable final String lobby) throws JsonProcessingException {
log.info("Send lobby infos to newly joined player");
final Message joinLobbyMessage = new JoinLobbyMessage(lobbyManagerService.getLobby(lobby).getPlayerNames());
final MessageWrapper joinLobbyMessageWrapped = websocketService.wrapMessage(joinLobbyMessage, Purpose.JOIN_LOBBY_MESSAGE);
simpMessagingTemplate.convertAndSendToUser(sha.getUser().getName(), newMember + lobby, joinLobbyMessageWrapped);
}
#MessageMapping("/lobby/{lobby}/join/team/{team}/player/{player}")
public void joinTeam(#DestinationVariable final String lobby, #DestinationVariable final String team, #DestinationVariable final String player) throws JsonProcessingException {
log.info("player '{}' joined team '{}' in lobby '{}'", player, team, lobby);
final JoinTeamMessage joinTeamMessage = new JoinTeamMessage(team, player);
final MessageWrapper joinTeamMessageWrapped = websocketService.wrapMessage(joinTeamMessage, Purpose.JOIN_TEAM_MESSAGE);
simpMessagingTemplate.convertAndSend(lobbyDestination + lobby, joinTeamMessageWrapped);
}
#MessageMapping("/start/lobby/{lobby}")
public void startLobby(#DestinationVariable final String lobby) {
log.info("start lobby");
//todo this methods will be implemented later
}
}
CLIENT:
function connect() {
console.log("connect to lobby");
return new Promise((resolve, reject) => {
stompClientGame.value = Stomp.over(
new SockJS("/minigames/towercrush/api/v1/connect")
);
let uuid = generateUUID();
console.log("players uuid was: ", uuid);
stompClientGame.value.connect(
{ player: player.value, lobby: lobby.value, userUUID: uuid },
() => resolve(stompClientGame.value)
);
});
}
function connectToLobby() {
connect()
.then(() => {
stompClientGame.value.subscribe(
"/user/queue/newMember/" + lobby.value,
function (messageOutput: any) {
handleMessageReceipt(messageOutput.body);
}
);
})
.then(() => {
stompClientGame.value.subscribe(
"/topic/lobby/" + lobby.value,
function (messageOutput: any) {
handleMessageReceipt(messageOutput.body);
}
);
});
}
i tryed literally every possible variation that came to my mind but nothing worked. pls help.

Concise HAL+JSON and JSON endpoint implementation?

Is it possible to concisely implement a single HAL-JSON & JSON endpoints in Spring Boot 2? The goal is to have:
curl -v http://localhost:8091/books
return this application/hal+json result:
{
"_embedded" : {
"bookList" : [ {
"title" : "The As",
"author" : "ab",
"isbn" : "A"
}, {
"title" : "The Bs",
"author" : "ab",
"isbn" : "B"
}, {
"title" : "The Cs",
"author" : "cd",
"isbn" : "C"
} ]
}
and for this (and/or the HTTP Accept header since this is a REST API):
curl -v http://localhost:8091/books?format=application/json
to return the plain application/json result:
[ {
"title" : "The As",
"author" : "ab",
"isbn" : "A"
}, {
"title" : "The Bs",
"author" : "ab",
"isbn" : "B"
}, {
"title" : "The Cs",
"author" : "cd",
"isbn" : "C"
} ]
with minimal controller code. These endpoints work as expected:
#GetMapping("/asJson")
public Collection<Book> booksAsJson() {
return _books();
}
#GetMapping("/asHalJson")
public CollectionModel<Book> booksAsHalJson() {
return _halJson(_books());
}
#GetMapping
public ResponseEntity<?> booksWithParam(
#RequestParam(name="format", defaultValue="application/hal+json")
String format) {
return _selectedMediaType(_books(), format);
}
#GetMapping("/asDesired")
public ResponseEntity<?> booksAsDesired() {
return _selectedMediaType(_books(), _format());
}
with the following helpers:
private String _format() {
// TODO: something clever here...perhaps Spring's content-negotiation?
return MediaTypes.HAL_JSON_VALUE;
}
private <T> static CollectionModel<T> _halJson(Collection<T> items) {
return CollectionModel.of(items);
}
private <T> static ResponseEntity<?> _selectedMediaType(
Collection<T> items, String format) {
return ResponseEntity.ok(switch(format.toLowerCase()) {
case MediaTypes.HAL_JSON_VALUE -> _halJson(items);
case MediaType.APPLICATION_JSON_VALUE -> items;
default -> throw _unknownFormat(format);
});
}
but the booksWithParam implementation is too messy to duplicate for each endpoint. Is there a way to get to, or close to, something like the booksAsDesired implementation or something similarly concise?
One way you could tell Spring that you want to support plain JSON is by adding a custom converter for such media types. This can be done by overwriting the extendMessageConverters method of WebMvcConfigurer and adding your custom converters there like in the sample below:
import ...PlainJsonHttpMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.config.EnableSpringDataWebSuport;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servelt.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import javax.annotation.Nonnull;
#Configuration
#EnableSpringeDataWebSupport
public class WebMvcConfiguration implements WebMvcConfigurer {
#Override
public void extendMessageConverters(#Nonnull final List<HttpMessageConverter<?>> converters) {
converters.add(new PlainJsonHttpMessageConverter());
}
}
The message converter itself is also no rocket-science as can be seen by the PlainJsonHttpMessageConverter sample below:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsr310.JavaTimeModule;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import javax.annotation.Nonnull;
#Component
public class PlainJsonHttpMessageConverter extends AbstractJackson2HttpMessageConverter {
public PlainJsonHttpMessageConverter() {
super(new ObjectMapper(), MediaType.APPLICATION_JSON);
// add support for date and time format conversion to ISO 8601 and others
this.defaultObjectMapper.registerModule(new JavaTimeModule());
// return JSON payload in pretty format
this.defaultObjectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}
#Override
protected boolean supports(#Nonnull final Class<?> clazz) {
return RepresentationModel.class.isAssignableFrom(clazz);
}
}
This should enable plain JSON support besides HAL-JSON without you having to do any further branching or custom media-type specific conversion within your domain logic or service code.
I.e. let's take a simple task as example case. Within a TaskController you might have a code like this
#GetMapping(path = "/{taskId:.+}", produces = {
MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE,
MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE
})
public ResponseEntity<?> task(#PathVariable("taskId") String taskId,
#RequestParam(required = false) Map<String, String> queryParams,
HttpServletRequest request) {
if (queryParams == null) {
queryParams = new HashMap<>();
}
Pageable pageable = RequestUtils.getPageableForInput(queryParams);
final String caseId = queryParams.get("caseId");
...
final Query query = buildSearchCriteria(taskId, caseId, ...);
query.with(pageable);
List<Task> matches = mongoTemplate.find(query, Task.class);
if (!matches.isEmpty()) {
final Task task = matches.get(0);
return ResponseEntity.ok()
.eTag(Long.toString(task.getVersion())
.body(TASK_ASSEMBLER.toModel(task));
} else {
if (request.getHeader("Accept").contains(MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE)) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.contentType(MediaTypes.HTTP_PROBLEM_DETAILS_JSON)
.body(generateNotFoundProblem(request, taskId));
} else {
final String msg = "No task with ID " + taskId + " found";
throw new ResponseStatusException(HttpStatus.NOT_FOUND, msg);
}
}
}
which simply retrieves an arbitrary task via its unique identifier and returns the representation for it according to the one specified in the Accept HTTP header. The TASK_ASSEMBLER here is just a custom Spring HATEOAS RepresentationModelAssembler<Task, TaskResource> class that converts task objects to task resources by adding links for certain related things.
This can now be easily tested via Spring MVC tests such as
#Test
public void halJson() throws Exception {
given(mongoTemplate.find(any(Query.class), eq(Task.class)))
.willReturn(setupSingleTaskList());
final ResultActions result = mockMvc.perform(
get("/api/tasks/taskId")
.accept(MediaTypes.HAL_JSON_VALUE)
);
result.andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON_VALUE));
// see raw payload received by commenting out below line
// System.err.println(result.andReturn().getResponse().getContentAsString());
verifyHalJson(result);
}
#Test
public void plainJson() throws Exception {
given(mongoTemplate.find(any(Query.class), eq(Task.class)))
.willReturn(setupSingleTaskList());
final ResultActions result = mockMvc.perform(
get("/api/tasks/taskId")
.accept(MediaType.APPLICATION_JSON_VALUE)
);
result.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE));
// see raw payload received by commenting out below line
// System.err.println(result.andReturn().getResponse().getContentAsString());
verifyPlainJson(result);
}
...
private void verifyHalJson(final ResultActions action) throws Exception {
action.andExpect(jsonPath("taskId", is("taskId")))
.andExpect(jsonPath("caseId", is("caseId")))
...
.andExpect(jsonPath("_links.self.href", is(BASE_URI + "/tasks/taskId")))
.andExpect(jsonPath("_links.up.href", is(BASE_URI + "/tasks")));
}
rivate void verifyPlainJson(final ResultActions action) throws Exception {
action.andExpect(jsonPath("taskId", is("taskId")))
.andExpect(jsonPath("caseId", is("caseId")))
...
.andExpect(jsonPath("links[0].rel", is("self")))
.andExpect(jsonPath("links[0].href", is(BASE_URI + "/tasks/taskId")))
.andExpect(jsonPath("links[1].rel", is("up")))
.andExpect(jsonPath("links[1].href", is(BASE_URI + "/tasks")));
}
Note how links are presented here differently depending on which media type you've selected.

How to create an Exception with multiple messages?

I'm getting response from external service that looks like:
"status": {
"httpCode": "external service response code, e.g. 201",
"errors": [
{
"code": "error code, e.g. 13",
"message": "e.g. Invalid value for some variable"
},
{
"code": "12",
"message": "Invalid phone number format"
}
]
}
The errors list may have multiple objects. While returning the response from that external service to my frontend application, I'd like to show all the messages. How do I do that? As far as I know Exception related classes only have a single field called message.
Exceptions are mostly not special, they are just a type definition same as any other. You can have them do whatever you want, as long as they extend Throwable. For example:
public class MaciazServiceException extends Exception {
private final Map<Integer, String> codeToMessageMapping;
public MaciazServiceException(JSON json) {
// code here that pulls code and message apart and makes...
Map<Integer, String> codeToMessageMapping = ....;
this.codeToMessageMapping = codeToMessageMapping;
}
#Override public String getMessage() {
// code here that returns a nice view of the above. For example...
return codeToMessageMapping.entrySet().stream().map(
entry -> entry.getKey() + " = " + entry.getValue())
.collect(Collectors.joining("\n"));
}
// you can define methods too, if you want:
public boolean hasErrorCode(int code) {
return codeToMessageMapping.containsKey(code);
}
}
This can then be used elsewhere:
try {
myMaciazService.doThingie(...);
} catch (MaciazServiceException e) {
if (e.hasErrorCode(MaciazService.ERRORCODE_NOT_AUTHORIZED)) {
userpassView.show();
}
}
One of the way to customize the Http response for exceptions, by using the Custom Exceptions.
1.Create a custom exception by using RuntimeException Class
public class SampleException extends RuntimeException {
private List<SampleNestedObject> messages;
public SampleException() {
}
public SampleException(List<SampleNestedObject> messages){
this.messages=messages;
}
}
public class SampleNestedObject {
private int httpCode;
private String message;
//Getters,Setters,Contructors
}
2.Create a response Structure
public class SampleErrorResponse {
private List<SampleNestedObject> messages;
//Getters,Setters,Contructors
}
Create an ExceptionHandler
#ExceptionHandler(SampleException.class)
public final ResponseEntity<Object> handleSampleException(SampleException ex,WebRequest request){
SampleErrorResponse errorResponse=new SampleErrorResponse(ex.getMessages());
return new ResponseEntity(errorResponse,HttpStatus.NOT_FOUND); //Any status code
}
4.Throw exception whenever you want to.
#GetMapping("/getException")
public ResponseEntity<Object> getException(){
List<SampleNestedObject> messages= Arrays.asList(new SampleNestedObject(404,"Sample Message 1"),new SampleNestedObject(404,"Sample Message 2"));
throw new SampleException(messages);
}
Response for the above sample will be,
{
"messages": [
{
"httpCode": 404,
"message": "Sample Message 1"
},
{
"httpCode": 404,
"message": "Sample Message 2"
}
]
}
Hope this will work.

Exception in combination of #DirtiesContext and FlywayConfig bean in integration test

I have a problem when adding new test. And the problem is I think related to #DirtiesContext. I tried removing and adding it but nothing works in combination. Test 1 is using Application Context as well.
the following two are running together and no issue.
Test 1
#ActiveProfiles({"aws", "local"})
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UnauthorizedControllerTest {
private static final Logger LOGGER = LoggerFactory.getLogger(UnauthorizedControllerTest.class);
#Autowired
private TestRestTemplate testRestTemplate;
#LocalServerPort
private int port;
#Autowired
private ApplicationContext context;
private Map<Class<?>, List<String>> excludedMethodsPerController;
#Before
public void setUp() {
excludedMethodsPerController = excludedMethodsPerController();
}
#Test
public void contextStarts() {
assertNotNull(context);
}
#Test
public void controllerCall_WithoutAuthorization_ReturnsUnauthorized() {
Map<String, Object> controllerBeans = context.getBeansWithAnnotation(Controller.class);
for (Object controllerInstance : controllerBeans.values()) {
LOGGER.info("Checking controller {}", controllerInstance);
checkController(controllerInstance);
}
}
public void checkController(Object controllerInstance) {
// Use AopUtils for the case that spring wraps the controller in a proxy
Class<?> controllerClass = AopProxyUtils.ultimateTargetClass(controllerInstance);
Method[] allMethods = controllerClass.getDeclaredMethods();
for (Method method : allMethods) {
LOGGER.info("Checking method: {}", method.getName());
if (!isCallable(controllerClass, method)) {
continue;
}
String urlPrefix = urlPrefix(controllerClass);
Mapping mapping = Mapping.of(method.getAnnotations());
for (String url : mapping.urls) {
for (RequestMethod requestMethod : mapping.requestMethods) {
ResponseEntity<String> exchange = exchange(urlPrefix + url, requestMethod);
String message = String.format("Failing %s.%s", controllerClass.getName(), method.getName());
assertEquals(message, HttpStatus.UNAUTHORIZED, exchange.getStatusCode());
}
}
}
}
private ResponseEntity<String> exchange(String apiEndpoint, RequestMethod requestMethod) {
return testRestTemplate.exchange(url(replacePathVariables(apiEndpoint)), HttpMethod.resolve(requestMethod.name()), null, String.class);
}
private String urlPrefix(Class<?> aClass) {
if (!aClass.isAnnotationPresent(RequestMapping.class)) {
return "";
}
RequestMapping annotation = aClass.getAnnotation(RequestMapping.class);
return annotation.value()[0];
}
private String url(String url) {
return "http://localhost:" + port + url;
}
private boolean isCallable(Class<?> controller, Method method) {
return Modifier.isPublic(method.getModifiers())
&& !isExcluded(controller, method)
&& !isExternal(controller);
}
private boolean isExcluded(Class<?> controller, Method method) {
List<String> excludedMethodsPerController = this.excludedMethodsPerController.getOrDefault(controller, new ArrayList<>());
return excludedMethodsPerController.contains(method.getName());
}
private boolean isExternal(Class<?> controller) {
return controller.getName().startsWith("org.spring");
}
private String replacePathVariables(String url) {
return url.replaceAll("\\{[^\\/]+}", "someValue");
}
/**
* There must be a really good reason to exclude the method from being checked.
*
* #return The list of urls that must not be checked by the security
*/
private static Map<Class<?>, List<String>> excludedMethodsPerController() {
Map<Class<?>, List<String>> methodPerController = new HashMap<>();
methodPerController.put(AuthenticationController.class, Collections.singletonList("generateAuthorizationToken"));
methodPerController.put(SystemUserLoginController.class, Arrays.asList("systemUserLogin", "handleException"));
methodPerController.put(ValidationController.class, Collections.singletonList("isValid"));
return methodPerController;
}
}
Test 2
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles({"aws", "local"})
public class RoleAdminControllerAuditTest {
private static final String DOMAIN_NAME = "testDomain";
private static final String APP_NAME_1 = "testApp_1";
private static final String APP_NAME_2 = "testApp_2";
private static final String ROLE_NAME = "testRole";
private static final String USER_NAME = "testUser";
#Autowired
AuditRepository auditRepository;
#Autowired
RoleAdminController roleAdminController;
#MockBean
RoleAdminService roleAdminService;
#MockBean
RoleAdminInfoBuilder infoBuilder;
#MockBean
AppInfoBuilder appInfoBuilder;
#MockBean
BoundaryValueService boundaryValueService;
#MockBean
RoleService roleService;
#MockBean
private SecurityService securityService;
private static final String URS_USER = "loggedInUser";
private static final String BOUNDARY_VALUE_KEY = "11";
private static final String BOUNDARY_VALUE_NAME = "Schulenberg";
private String auditEventDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
#BeforeClass
public static void setupTestEnv() {
// https://github.com/localstack/localstack/issues/592
System.setProperty("com.amazonaws.sdk.disableCbor", "true");
}
#Before
public void setUp() throws Exception {
auditRepository.clean();
when(securityService.getLoggedInUser()).thenReturn(new TestHelper.FakeUser(URS_USER));
//when(roleService.addRoleToApp(any(), any(), eq(ROLE_NAME))).thenReturn(TestHelper.initRole(ROLE_NAME));
when(boundaryValueService.findBoundaryValueById(eq(123L))).thenReturn(initBoundaryValue(BOUNDARY_VALUE_KEY, BOUNDARY_VALUE_NAME));
when(boundaryValueService.findBoundaryValueById(eq(666L))).thenReturn(initBoundaryValue(BOUNDARY_VALUE_KEY, BOUNDARY_VALUE_NAME));
}
#Test
public void addUserAsRoleAdminLogged() throws UserIsAlreadyRoleAdminException, RoleNotFoundException, BoundaryValueNotFoundException {
User user = initUser(USER_NAME);
List<RoleAdminInfo> roleAdminInfos = getRoleAdminInfos();
roleAdminController.addUserAsRoleAdmin(user, roleAdminInfos);
List<String> result = auditRepository.readAll();
assertEquals("some data", result.toString());
}
#Test
public void removeUserAsRoleAdminLogged() throws RoleNotFoundException, BoundaryValueNotFoundException {
User user = initUser(USER_NAME);
Long roleId = Long.valueOf(444);
Role role = initRole("test-role");
role.setApp(initApp("test-app"));
role.setDomain(initDomain("test-domain"));
when(roleService.getRoleByIdOrThrow(roleId)).thenReturn(role);
roleAdminController.removeUserAsRoleAdmin(user, roleId, Long.valueOf(666));
List<String> result = auditRepository.readAll();
assertEquals("some data", result.toString());
}
#Test
public void removeRoleAdminPermission() throws RoleNotFoundException, BoundaryValueNotFoundException {
User user = initUser(USER_NAME);
List<RoleAdminInfo> roleAdminInfos = getRoleAdminInfos();
roleAdminController.removeRoleAdminPermission(user, roleAdminInfos);
List<String> result = auditRepository.readAll();
assertEquals(1, result.size());
assertEquals("some data", result.toString());
}
private List<RoleAdminInfo> getRoleAdminInfos() {
RoleAdminInfo info1 = initRoleAdminInfo(DOMAIN_NAME, ROLE_NAME, APP_NAME_1);
RoleAdminInfo info2 = initRoleAdminInfo(DOMAIN_NAME, ROLE_NAME, APP_NAME_2);
info1.setBoundaryValueId(123L);
info1.setBoundaryValueKey(BOUNDARY_VALUE_KEY);
info1.setBoundaryValueName(BOUNDARY_VALUE_NAME);
return Arrays.asList(info1, info2);
}
}
Test 3 (newly added one)
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = FlywayConfig.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
#ActiveProfiles({"aws", "local"})
public class BoundaryValueDeltaControllerTest {
private static final String API_V1 = "/api/v1/";
#Autowired
TestRestTemplate testRestTemplate;
#Autowired
private DomainBuilder domainBuilder;
#Autowired
private AppBuilder appBuilder;
#Autowired
private UserBuilder userBuilder;
#Autowired
private DomainAdminBuilder domainAdminBuilder;
#Autowired
private BoundarySetBuilder boundarySetBuilder;
#MockBean
private LoginUserProvider loginUserProvider;
#MockBean
private LoginTokenService loginTokenService;
#MockBean
private BoundaryServiceAdapter serviceAdapter;
#LocalServerPort
private int port;
LoginUserInfo loggedInUser;
#Before
public void setUp() {
clear();
}
#After
public void tearDown() {
clear();
}
#Test
public void updateBoundaryValuesFromApi() throws UrsBusinessException {
Domain domain = domainBuilder.persist();
App app = appBuilder.persist(domain);
BoundarySet boundarySet = boundarySetBuilder.persist(domain);
User user = userBuilder.persist(domain.getAuthor().getUsername());
aLoggedInUser(domain.getAuthor().getUsername());
domainAdminBuilder.persist(user, domain);
mockReadInfoFromApiUsingApp();
ResponseEntity<String> response = callUpdateBoundaryValuesFromApi(domain, boundarySet, app);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
}
private void mockReadInfoFromApiUsingApp() throws UrsBusinessException {
BoundaryValueInfo boundaryValueInfo = new BoundaryValueInfo();
boundaryValueInfo.setBoundaryValueId(10L);
boundaryValueInfo.setBoundaryValueKey("boundaryValueKey");
boundaryValueInfo.setBoundaryValueName("boundaryValuename");
when(serviceAdapter.readInfoFromApiUsingApp(any(), any(), any())).thenReturn(new BoundaryValueInfo[]{boundaryValueInfo});
}
private ResponseEntity<String> callUpdateBoundaryValuesFromApi(Domain domain, BoundarySet boundarySet, App app) {
String url = url(API_V1 + "domains/" + domain.getName() + "/boundarysets/" + boundarySet.getBoundarySetName() + "/app/" + app.getName()+ "/updatefromapi/");
return testRestTemplate.exchange(url,HttpMethod.GET, null, String.class);
}
private String url(String url) {
return "http://localhost:" + port + url;
}
private void aLoggedInUser(String username) {
Claims claims = Jwts.claims();
claims.put("username", username);
loggedInUser = LoginUserInfo.parse(claims);
when(loginUserProvider.getLoggedInUser()).thenReturn(loggedInUser);
when(loginTokenService.parseToken(any())).thenReturn(loggedInUser);
}
private void clear() {
appBuilder.deleteAll();
boundarySetBuilder.deleteAll();
domainAdminBuilder.deleteAll();
domainBuilder.deleteAll();
userBuilder.deleteAll();
}
}
Flyway config
#TestConfiguration
public class FlywayConfig {
#Bean
public FlywayMigrationStrategy clean() {
return flyway -> {
flyway.clean();
flyway.migrate();
};
}
}
And I am getting below exception while running all together.
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java.....
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Invocation of init method failed; nested exception is org.flywaydb.core.internal.exception.FlywaySqlException:
Unable to obtain connection from database: Too many connections
---------------------------------------------------------------
SQL State : 08004
Error Code : 1040
Message : Too many connections
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1762)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
... 49 more
Caused by: org.flywaydb.core.internal.exception.FlywaySqlException:
Unable to obtain connection from database: Too many connections
I am struggling since yesterday's and you might find duplicate but I tried to add the more details today. please guide me here.
you must add the configuration for flyway
flyway.url=jdbc:postgresql://xxx.eu-west-2.rds.amazonaws.com:5432/xxx
flyway.user=postgres
flyway.password=xxx

RestAssured Ignore NoParameterValue exception when param variables are not mentioned in the url

I have defined a global RequestSpecification with some pathParams and using the spec in all requests. But not all the params used in the global spec would be used in requests.
Assume I have mentioned 3 in the globalSpec and in some requests I would only be needing 2 params and in some 1 and in some 0 params.
But Rest-Assured is throwing NoParameterValue exception
Invalid number of path parameters. Expected 1, was 2. Redundant path parameters are: customerOrderId=io.restassured.internal.NoParameterValue#753b84c6.
java.lang.IllegalArgumentException: Invalid number of path parameters. Expected 1, was 2. Redundant path parameters are
Is there any inbuilt config/ignore mechanism that I can use to make rest-assured not throw exceptions?
Sample Code
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import static io.restassured.RestAssured.given;
public class TestRestAssuredParams {
private RequestSpecification defaultRequestSpec = new RequestSpecBuilder()
.addPathParams(
"env", "value1",
"customer", "value2",
"order", "value3"
)
.setContentType(ContentType.JSON)
.build();
private void reqWithNoParam() {
given()
.spec(defaultRequestSpec)
.get("https://www.testapi.com/dev");
}
private void reqWith1Param() {
given()
.spec(defaultRequestSpec)
.get("https://www.testapi.com/{env}");
}
private void reqWith2Param() {
given()
.spec(defaultRequestSpec)
.get("https://www.testapi.com/{env}/{customer}");
}
private void reqWith3Param() {
given()
.spec(defaultRequestSpec)
.get("https://www.testapi.com/{env}/{customer}/order/{order}");
}
public static void main(String[] args) {
TestRestAssuredParams testRestAssuredParams = new TestRestAssuredParams();
testRestAssuredParams.reqWithNoParam();
testRestAssuredParams.reqWith1Param();
testRestAssuredParams.reqWith2Param();
testRestAssuredParams.reqWith3Param();
}
}
How to make sure RestAssured handle the above scenario.
RestAssured version - 4.3.0
Java - 11.0.5
You will have to use the removeParam of FilterableRequestSpecification,
private void reqWithNoParam() {
given().filter((requestSpec, responseSpec, ctx) -> {
requestSpec.removePathParam("env");
requestSpec.removePathParam("customer");
requestSpec.removePathParam("order");
return ctx.next(requestSpec, responseSpec);
}).spec(defaultRequestSpec).log().all().get("https://www.testapi.com/dev");
}
private void reqWith1Param() {
given().filter((requestSpec, responseSpec, ctx) -> {
requestSpec.removePathParam("customer");
requestSpec.removePathParam("order");
return ctx.next(requestSpec, responseSpec);
}).spec(defaultRequestSpec).log().all().get("https://www.testapi.com/{env}");
}
private void reqWith2Param() {
given().filter((requestSpec, responseSpec, ctx) -> {
requestSpec.removePathParam("order");
return ctx.next(requestSpec, responseSpec);
}).spec(defaultRequestSpec).log().all().get("https://www.testapi.com/{env}/{customer}");
}
private void reqWith3Param() {
given().spec(defaultRequestSpec).log().all().get("https://www.testapi.com/{env}/{customer}/order/{order}");
}
removePathParam in the filter does the trick.
FilterableRequestSpecification has a methond getPathParamPlaceholders which will give the params from the url mapped in the request. I removed the unneeded pathParams using that.
private RequestSpecification defaultRequestSpec = new RequestSpecBuilder()
.addPathParams(
"env", "value1",
"customer", "value2",
"order", "value3"
)
.addFilter((requestSpec, responseSpec, ctx) -> {
var paramsFromRequest = requestSpec.getPathParamPlaceholders();
var finalRequestSpec = requestSpec;
finalRequestSpec
.getPathParams()
.forEach((key, val) -> {
if (!paramsFromRequest.contains(key)) {
finalRequestSpec.removePathParam(key);
}
});
return ctx.next(finalRequestSpec, responseSpec);
})
.setContentType(ContentType.JSON)
.build();

Categories