Service end point with path variable is causing 404 with cloud contract - microservices

I wrote a contract and the plugin autogenerated tests out of it. I'm seeing a very strange behavior with these autogenerated tests.
Following is my service endpoint:
#RequestMapping(value="/check/{id}" method= RequestMethod.GET, produces = Media.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Application>> getApplications(
#PathVariable (value = "id") String id){
return appService.findAll(id);
}
And here is the contract:
Contract.make {
request {
method GET()
url '/check/1234567'
}
response {
status 200
body("""
{
.........
}
""")
headers {
contentType(applicationJson())
}
}
}
As I run "mvn clean install" tests are autogenerated and run. This works fine with the above contract and test passes perfectly.
However, if I change the data in the path to "/check/12345678" it starts failing.
The thing that I'm not able to understand is my endpoint is taking id path varaible which is a String type. For this type of path any value should be good. However the following paths work:
url '/check/1234567'
url '/check/12'
url '/check/12347'
And following doesn't work:
url '/check/12345678' //added just one more digit
url '/check/aa4567' //prepended characters
url '/check/123aa' //appended characters
It would be great If I can get an explanation about this behavior, or how to resolve it. Practically any string should work. For example "/check/234df-dfs-fs234fds-sdf-fssd3rr"

You may try with urlPattern instead of url, replace
url '/check/1234567'
for
urlPattern '/check/[0-9]+'

Related

How to write async data to remote endpoint without getting "No suitable writer found exception"?

I have the following controller method:
#PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, path = "/upload")
public Mono<SomeResponse> saveEnhanced(#RequestPart("file") Mono<FilePart> file) {
return documentService.save(file);
}
which calls a service method where I try to use a WebClient to put some data in another application:
public Mono<SomeResponse> save(Mono<FilePart> file) {
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.asyncPart("file", file, FilePart.class);
bodyBuilder.part("identifiers", "some static content");
return WebClient.create("some-url").put()
.uri("/remote-path")
.syncBody(bodyBuilder.build())
.retrieve()
.bodyToMono(SomeResponse.class);
}
but I get the error:
org.springframework.core.codec.CodecException: No suitable writer found for part: file
I tried all variants of the MultipartBodyBuilder (part, asyncpart, with or without headers) and I cannot get it to work.
Am I using it wrong, what am I missing?
Regards,
Alex
I found the solution after getting a reply from one of the contributes on the Spring Framework Github issues section.
For this to work:
The asyncPart method is expecting actual content, i.e. file.content(). I'll update it to unwrap the part content automatically.
bodyBuilder.asyncPart("file", file.content(), DataBuffer.class)
.headers(h -> {
h.setContentDispositionFormData("file", file.name());
h.setContentType(file.headers().getContentType());
});
If both headers are not set then the request will fail on the remote side, saying it cannot find the form part.
Good luck to anyone needing this!

Controller returning inputstream - content negotiation and media types

Introduction
I have a question about a RestController and a Test .
I have the following PostMapping:
#PostMapping(path = "/download/as/zip/{zipFileName}" )
#ResponseBody
public ResponseEntity<InputStreamResource> downloadDocumentZip(#RequestHeader(required=false,name="X-Application") String appName, #RequestBody ZipFileModel zipFileModel, #PathVariable("zipFileName") String zipFileName)
And I have the following Test:
Response response = given(this.requestSpecification).port(port)
.filter(document("downloadAsZip",
preprocessRequest(prettyPrint()),
requestHeaders(headerWithName("X-Application").description("Owner application")),
pathParameters(parameterWithName("zipFileName").description("The name of the resulting zip file. Mostly not needed/optional.")))
)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.header(new Header(HEADER, "themis"))
.body(jsonContent)
.when()
.post("/download/as/zip/{zipFileName}", "resultFile.zip");
This works and 200 is returned.
First Question
Now I am a bit confused about the meaning of .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE) within the Test.
Content-type is the header on the returned response. But in this test it is being included while making the test request ? Or is it signifying in this case that we are sending JSON in the request body?
Second Question
I know that my controller method should consume JSON, and returns Bytes.
So hence, I make the following change:
#PostMapping(path = "/download/as/zip/{zipFileName}", consumes = MediaType.APPLICATION_JSON_VALUE)
This works so far.
So then I add the following:
#PostMapping(path = "/download/as/zip/{zipFileName}", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
And it fails:
java.lang.AssertionError:
Expected :200
Actual :406
<Click to see difference>
So I changed my test to be the following:
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.accept(MediaType.APPLICATION_OCTET_STREAM_VALUE)
This fails once again.
Expected :200
Actual :406
So even though the client is signalling the same accept header as what the controller produces, we have an error.
Questions:
So should we or should we not have the produces= on the request mapping?
Why is it failing now? Is there a conflict in consuming JSON and Producing Bytes? Or the ContentType within the test?
The problem is that spring changes the return content-type if the end of a URL has an extension.
So seeing .zip at the end, was causing spring to over-ride the type to application/zip .

Fetching Form-data in spring controller

Hi It might look like duplicate but its not.
I am building a rest api using spring boot and need to fetch form-data sent by client app in POST request.
for testing purpose I am using postman. So far i have tried below
#PostMapping("/feed/comment/add/{feedId}")
public ResponseEntity<BaseResponse> addComment(#RequestHeader(name = Constants.USER_ID_HEADER) int userId,
#PathVariable("feedId") int feedId,
#RequestParam("comment") String comment
) {
LOGGER.info("Received add comment request with comment:"+comment);
return new ResponseEntity<BaseResponse>(new BaseResponse("You are not feed owner", RESPONSETYPE.ERROR), HttpStatus.UNAUTHORIZED);
}
this gives error "Required String parameter 'comment' is not present"
Second way tried:
#PostMapping("/feed/comment/add/{feedId}")
public ResponseEntity<BaseResponse> addComment(#RequestHeader(name = Constants.USER_ID_HEADER) int userId,
#PathVariable("feedId") int feedId,
#RequestParam Map<String, String> values
) {
for(String key: values.keySet()) {
System.out.println(key+":"+values.get(key));
}
return new ResponseEntity<BaseResponse>(new BaseResponse("You are not feed owner", RESPONSETYPE.ERROR), HttpStatus.UNAUTHORIZED);
}
this gives wired output:
------WebKitFormBoundarymk97RU1BbJyR0m3F
Content-Disposition: form-data; name:"comment"
test comment
------WebKitFormBoundarymk97RU1BbJyR0m3F--
I'm pretty sure that with plane servlet i can access this using request.getParameter("comment")
not sure how i can fetch it in case of spring rest controller.
"Required String parameter 'comment' is not present" this error happens when this paremeter is required but you didn't send it.
#RequestParam(value="comment", required=false)
this will make the comment parameter optional. So if you missed sending the comment parameter its ok.

Web API JSON Formatter StringEscapeHandling ignored

When I do this:
config.Formatters.JsonFormatter.SerializerSettings.StringEscapeHandling = StringEscapeHandling.EscapeHtml;`
and then hit this endpoint:
[HttpGet]
public object TestApi()
{
return new {test = "<script>alert('o hai');</script>"};
}
it returns the unescaped string. All of the examples I've seen of the StringEscapeHandling are those manually using JsonConvert.SerializeObject.
Other JsonFormatter.SerializerSettings that I set work just fine except for that one. Is this not supported?

Empty request body gives 400 error

My Spring controller method looks something like this:
#RequestMapping(method=RequestMethod.PUT, value="/items/{itemname}")
public ResponseEntity<?> updateItem(#PathVariable String itemname, #RequestBody byte[] data) {
// code that saves item
}
This works fine except when a try to put a zero-length item, then I get an HTTP error: 400 Bad Request. In this case my method is never invoked. I was expecting that the method should be invoked with the "data" parameter set to a zero-length array.
Can I make request mapping work even when Content-Length is 0?
I am using Spring framework version 4.1.5.RELEASE.
Setting a new byte[0] will not send any content on the request body. If you set spring MVC logs to TRACE you should see a message saying Required request body content is missing as a root cause of your 400 Bad Request
To support your case you should make your #RequestBody optional
#RequestMapping(method=RequestMethod.PUT, value="/items/{itemname}")
public ResponseEntity<?> updateItem(#PathVariable String itemname, #RequestBody(required = false) byte[] data) {
// code that saves item
}

Resources