MockMvc test failing and giving a 406 - spring-boot

I've created a mockMvc integration test to test one of my controller endpoints. It running through the code fine, all the way to producing a 200, but the mock can't read the output type and is producing a 406 instead. This issue has come up several times around the place but I've tried loads of the fixes and can't seem to get it to work. If there's anyone who can help I'd be very grateful. NB I've tried setting content type to application json but this doesn't seem to have done anything.
#PostMapping (path = "/person/{gender}", produces = "application/json")
public ResponseEntity<Person> selectPerson(#PathVariable String gender){
DataGenerator dataGenerator = new DataGenerator();
return dataGenerator.getPersonObject(gender);
}
#Test
public void personEndpointsReturn200HttpStatusWithAcceptableFemaleParameter()
throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/person/{name}", "Female"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
MockHttpServletRequest:
HTTP Method = POST
Request URI = /person/Female
Parameters = {}
Headers = {Content-Type=[application/json;charset=utf-8]}
Body = null
Session Attrs = {}
Handler:
Type = dvsa.seo.asessment.controller.PersonController
Method = public.
org.springframework.http.ResponseEntity<dvsa.seo.asessment.models.Person>
dvsa.seo.asessment.controller.PersonController.selectPerson(java.lang.String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotAcceptableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 406
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :406
<Click to see difference>

Related

how to return a specific status code in Kotlin

I've set up a route that when I get a name in my post body I will search the DB and return an ID value.
What I want to do is once there is no ID present in the DB return a 204 status code.
But should that be handled in the service or in my controller?
and
How do I return my specific status code?
#ResponseStatus(HttpStatus.OK)
#PostMapping("/ID_values/")
fun getID(
#RequestBody
name: String
): ResponseEntity<String> = ResponseEntity.ok(IDLookupService.lookupIDValue(name))
}
#Service
class EmailLookupService(
private val IDRepo: IDRepo
) : Logging {
fun lookupIDValue(name: String): String {
val IDLookupResult = IDRepo.findById(name)
return if (IDLookupResult.isPresent) {
IDLookupResult.get().ID.toString()
} else {
// return status code 204
}
}
}
First, you should omit the #ResponseStatus(HttpStatus.OK) annotation if you do not wish to always return a status code of 200. Using that annotation, it would suffice to only specify the response body as return value (i.e specify return type String and then return only result in your example), and Spring would automatically wrap that into a response entity with HTTP-status OK.
Second, you need some way to tell the caller of IDLookupService.lookupIDValue (which should probably be called on an instance of IDLookupService and not the class itself) that there was nothing found. This could be done for instance by changing the return type to String? and return null if nothing was found.
Then you can change getID to return
val result = idLookupService.lookupIDValue(name)
return if(result != null) ResponseEntity.ok(result)
else ResponseEntity("not found", HttpStatus.NO_CONTENT)
If you wish to return something different than a String in the case there was nothing found (like an error object with detailed information; in the example here it is simply the text "not found"), you can change the response type of getID to ResponseEntity<*>.

Spring , update value if not null

I am working with Redmine API, and want to update an issue.
The issue has 30 variables, Subject, Description, Author, and Assignee ...
I have no problem updating the issue Subject and Description like this:
#RequestMapping(value = "/issues/update/{id}", method = RequestMethod.PUT)
public ResponseEntity<Object> issueUpdate(#RequestBody ObjectNode json, #PathVariable int id) throws RedmineException, RuntimeException, IllegalArgumentException {
String apiKey = json.get("API_KEY").asText();
RedmineManager mgr = RedmineManagerFactory.createWithApiKey("http://localhost:3001/", apiKey);
IssueManager issueManager = mgr.getIssueManager();
Issue issue = issueManager.getIssueById(id);
issue.setSubject(json.get("SUBJECT").asText())
.setDescription(json.get("DESCRIPTION").asText())
.update();
return new ResponseEntity<>(HttpStatus.OK);
}
The problem with this way is I am only allowed to change these 2 values and I have to include, SUBJECT and DESCRIPTION in my JSON request body.
If SUBJECT is not in JSON, then it will be considered as null and I get NullPointerException.
I want to make something more flexible and elegant, to allow the change of each value, and if not exist, don't set to null but keep the old values.
something logical to this but a bit smarter
if json.get("SUBJECT").asText() != null {
issue.setSubject(json.get("SUBJECT").asText()) //set if mentioned
} else {
issue.setSubject(issue.getSubject()) //set the old value
}
The idea is, now am available to have setSubject and all the other available setters in case it's mentioned in my JSON request or not.

Is there any situation QueryString is present but HttpServletRequest.getParameterMap() is empty?

I have encouterd an odd situation while we are doing press testing in our test env. When the app load is high, the Query String will missing occasionally and the Spring will throw MissingServletRequestParameterException.
In order to find the root cause , I add some logs at the foremost Filter(code is shown below), and something weired happened.
public static void printRequestParameter(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
log.info("Request URI : {}, method = {} , query string = {}", request.getRequestURI(), request.getMethod(), request.getQueryString());
if (MapUtils.isNotEmpty(parameterMap)) {
parameterMap.forEach((k, v) -> {
log.info("Request parameter name = {}, value = {}", k, ArrayUtils.isEmpty(v) ? Strings.EMPTY : Arrays.stream(v).collect(Collectors.joining(COMMA)));
});
}
}
The request.getParameterMap() is empty, but , the query string is not empty , and I got a log like :
Request URI : /a/b/c, method = POST , query string = foo=bar.
But no logs like:
Request parameter name = foo , value = bar
And our Controller use #RequestParam() String foo to receivce the parameter , finally , the Spring throws
MissingServletRequestParameterException Handler org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'foo' is not present
at org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.handleMissingValue(RequestParamMethodArgumentResolver.java:204)
at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:114)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)
at org.springframework.web.method.support.InvocableH
I just wonder, why the parameter in query string is not contained in parameterMap?
Note:
The odd behavior is only happened occasionally, at most time it's just works.
My spring boot version is 2.3.9.RELEASE and the embedded tomcat version is 9.0.43.
Any help is appreciated!
Since the specification does not allow ServletRequest.getParameterMap to throw any exception, any failure in parameter parsing will cause the parameter list to be empty.
To detect this situation you can log the "org.apache.catalina.parameter_parse_failed_reason" attribute of your request.
Examples of query strings that should fail:
?=noname,
?urlEncoding=%ue

ResponseEntity return type wildcard with swagger generated interface

I want to know the difference between returning ResponseEntity<?> with wildcard VS ResponseEntity<ABC.class> as return type when Swagger generated API interface contains 2 different classes as return types, i.e one for an exception and another for normal flow.
My controller interface is :-
#Operation(summary = "Get user by user name", tags = { "user" })
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = "successful operation", content = #Content(schema = #Schema(implementation = User.class))),
#ApiResponse(responseCode = "400", description = "Invalid username supplied", content = #Content(schema = #Schema(implementation = Error.class)))
})
#GetMapping(value = "/user/{username}", produces = { "application/xml", "application/json" })
ResponseEntity<?> getUserByName(#PathVariable("username") String username);
the #Schema defines return type.
Can I use ResponseEntity<User> instead of the <?> for the getUserByName method ?
I have the the global exception handler #ControllerAdvice in my application which returns ResponseEntity<Error> .
Answer for your question is simply Yes. Since you are using a Controller Adviser, you can directly define the endpoint return type as User in ResponseEntity
Using a Wildcard(ResponseEntity<?>) as a return type, the symbol ? defines that the return value of the controller method can be any type of ResponseEntity.
Using as ResponseEntity<?> or simply keep it as ResponseEntity<> are considered as raw type of its usage.
But really it is not a good practice to do so. So, you have to declare the exact return type of the controller method in this format ResponseEntity<ABC>.
Let's take this example java of method returning a list of objects(List<Object>).
It is possible on this list to add a Car.class type, a Van.class type. But how ever the consumer of a method should not have to deal with such disruptive questions and we have to define an exact type.
Yes you can use ResponseEntity<User> if your method returns a User type ResponseEntity.
I hope this'll help you to solve the issue :)

Spring MVC - Hit to URL with RequestParam failing with 404 Not Found Error

I am using Spring MVC in my project and while mapping user request to a URI, I get the 404 error. Here is my function skeleton which I want to be invoked:
#RequestMapping(value="/inventory/discovery", method = RequestMethod.GET, params = {"discoveryType"}, produces = {"application/json"})
public String getDiscoveryByType(#RequestParam("discoveryType") String discoveryType)
{
return discoveryType;
}
I am expecting this method to be called when I give the URL
http://<some-ip>/inventory/discovery/discoveryType?=DMVPN
However, when I test the code using Chrome's Advanced Rest Client I see that the URI it is trying to access is "/inventory/DMVPN" and not the "/inventory/discovery?discoveryType=DMVPN".
Am I missing something here? I don't see anything wrong with my URL syntax
I have another function in my code (but I don't think it is causing the problem) which has same request-mapping value, but has no params attribute.
#RequestMapping(value = "/inventory/discovery", method = RequestMethod.GET, produces = { "application/json" })
public ResponseEntity<DiscoveryNIOListResult> getAllDiscovery() {
logger.trace("getAllDiscovery");
List<DiscoveryNIO> listDiscoveryNIO = discoveryDasClient.getDiscoveryList();
DiscoveryNIOListResult result = new DiscoveryNIOListResult();
result.setResponse(listDiscoveryNIO);
return new ResponseEntity<DiscoveryNIOListResult>(result, HttpStatus.OK);
}
Ok, so a second look at your URL:
http://<some-ip>/inventory/discovery/discoveryType?=DMVPN
This is NOT passing discoveryType as a request GET parameter. For that you would need to do:
http://<some-ip>/inventory/discovery&discoveryType?=DMVPN
If you want to use path variables instead, you could do something like:
http://<some-ip>/inventory/discovery/DMVPN
And change your handler to something like:
#RequestMapping(value="/inventory/discovery/{discoveryType}", method = RequestMethod.GET, produces = {"application/json"})
public String getDiscoveryByType(#PathVariable("discoveryType") String discoveryType)
{
return discoveryType;
}
It looks like you were trying to mix these two methods, and that wont work.
Try changing your URL to:
http://<some-ip>/inventory/discovery?discoveryType=DMVPN

Resources