How to input attribute into the webtestclient - java

I have a controller in which it fetches a value from the attribute map.
The code responsible for fetching something in the attributes is this:
Map<String, Object> memberClaims = (Map<String, Object>) request.getAttribute("token");
And in my test this is how I've wired it up:
#Test
public void shouldReturn200()
{
webTestClient.post()
.uri(URL)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(order()), Token.class)
.attribute("token", "123abc")
.exchange()
.expectStatus()
.is2xxSuccessful();
}
But the .attribute seems to not have any effect. I've debugged and can see that the token map is not in the MockHttpServletRequest.. thus the test returns a 500 server response.. null pointer.
Anyone know how i can add attributes to the mock request?

You must be aware that .attribute(String, String) is not intended to send parameters from the client along with the request. It is used to put additional attributes to a request when it is already on server side.
An example would be if you have an interceptor in Spring adding further information after grabbing the request an forwarding the request afterwards. This is well explained here:
To send 'attributes' from the client side you have to add them as parameters as in this example:
var respSpec = webTestClient.post()
.uri( uriBuilder - > uriBuilder
.scheme("http")
.host("localhost")
.port("10080")
.path("/endpoint")
.queryParam("requestId", requestId)
.queryParam("aUrl", aUrl)
.build())
.header(apiSecretHeader, secret)
.header("HEADER_1", "header value 1")
.header("HEADER_2", "header value 2")
.exchange();
The REST endpoint could be defined this way:
#PostMapping(value = "/endpoint", produces = {MediaType.APPLICATION_JSON_VALUE})
#ResponseStatus(HttpStatus.ACCEPTED)
public ResponseEntity<?> myEndpoint(#RequestParam final String requestId, #NonNull #RequestParam(name = "url") final URL aUrl)

Related

RestTemplate for #RequestBody and #RequestParam

I'm trying to use RestTemplate to call POST api like that :
RequestorParam test = new RequestorParam();
test.setScopeMcg("MCG");
test.setSituatedDealIds(situatedDealIds);
String url = "http://localhost:" + serverPort + "/retrieveAttributes";
ResponseEntity<SituatedDeals> response = restTemplate.postForEntity(url, test, SituatedDeals.class);
and the code of the controller is like ;
#PostMapping(value = "/retrieveAttributes", produces = "application/json")
#ResponseBody
#Duration
public SituatedDeals retrieveAttributes(
#RequestParam String scopeMcg,
#RequestBody SituatedDealIds situatedDealIds
) {
log.info("success")
}
i'm getting bad request, can someone help ?
According to your controller code, you are actually not returning any Response Entity of type SituatedDeals, just logging it as success. this might be the reason for the null object in response.
The scopeMcg is a RequestParameter so you should be passing it in a request param format i.e http://localhost:8080/retrieveAttributes?scopeMcg=MCG
Reference:Spring Request Param
Your test Object is the actual payload for your post request which should be of type SituatedDealIds object.
Reference: Rest-Template post for Entity

Spring boot rest endpoint returning null with fetch request

I have already created Rest Endpoint in Java spring boot. It returns appropriate response when I request it via Postman. But when I use react fetch it does not show any response in browser if return is Json.
Spring boot controller:
#RestController
#RequestMapping(path = "/v1/test")
#AllArgsConstructor(onConstructor_ = {#Autowired})
public class TestController {
...
}
Below endpoint is returning appropriate response.
#GetMapping(value = "/helloWorld", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public String getHelloWorld() {
return "Hello, World1!";
}
But when I try to hit below endpoint it returns null when I make fetch request. But it returns appropriate response when I hit it via postman.
#GetMapping(value = "/testEndpoint", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public String returnTestResponse() {
HashMap<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("results", "value2");
return "{\"a\":1, \"b\":\"foo\"}";
}
Also tried returning POJO object. But still no response.
#GetMapping(value = "/testModel", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public SearchResultsModel testModel() {
this.myService.getSearchResult();
}
React fetch call:
await fetch(ALL_ARTICLES_ENDPOINT, {
mode: 'no-cors',
method: 'GET',
redirect: 'follow',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
}).then(response => {
console.log(response);
})
.then(data => {
console.log('Success:', data);
}).catch((error) => {
console.error('Error:', error);
});
Postman have couple hidden headers which are being sent with all requests.
Check Hide auto-generated headers
What you are missing in react call is is Accept header with application/json value
EDIT:
Just saw that you are returning string as json. You need to wrap it in POJO object and return it in returnTestResponse class
SECOND EDIT:
This will work. Try to implement your POJO
#GetMapping(value = "/testEndpoint", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public YourObject returnTestResponse() {
HashMap<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("results", "value2");
return new YourObject(map);
}
Issue was caused by adding mode: 'no-cors' option in fetch request. This option helped me to get rid of cors error but it means that in return I won't be able to see body and headers in chrome.
To resolve the issue I removed mode: 'no-cors' and added #CrossOrigin annotation on my spring boot controller.

Spring Conversion Magic

I am having a piece of code like below for calling one of our service.
MultiValueMap<String, String> parametersMap = new LinkedMultiValueMap<>();
parametersMap.add("query", query);
parametersMap.add("locale", "en_US");
parametersMap.add("resultsLimit", Boolean.FALSE.toString());
parametersMap.add("maxResults", maxResults);
parametersMap.add("type", "TTT");
parametersMap.add("ids", commaSeparatedValues(ids));
parametersMap.add("infoTypes", "HHH,JJJ");
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(parametersMap, getHttpHeaders());
MyEntity myEntity = restTemplate.postForEntity("http://example.com", httpEntity, MyEntity.class);
And at the server side the controller code is like
#RequestMapping("my/service")
public MyEntity suggest(#RequestParam(required = true) String query, #RequestParam(required = true) String locale,
#RequestParam(required = false) String resultsLimit, #Valid OtherOptions options)
and the OtherOption class is like
class OtherOptions {
String maxResults;
String type;
String ids;
String infoTypes;
}
Here everything is working fine, but I am confused about somethings like .
Is it a get or post request ?
How is some of the parameter maps content become request params(query params) and some others got mapped to the Object of OtherOptions ?
Which is the actual body of the request ?
Is it a get or post request ?
It is a post request. you are calling restTemplate.postForEntity. But your server side method is not restricted as you didn't specify the method attribute for RequestMapping so same method can handle any http method from the point of server.
How is some of the parameter maps content become request params(query params) and some others got mapped to the Object of OtherOptions?
None of them are query params.
See the spring docs for the meaning of #RequestParam. In your case, it all comes from body and not as query params
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html#postForEntity-java.net.URI-java.lang.Object-java.lang.Class-
The body of the entity, or request itself, can be a MultiValueMap to create a multipart request.
Which is the actual body of the request?
parametersMap is the body of the http request.
Note:
Currently your call should fail because you are posting it to http://example.com at client and listening at server side on my/service

Forward a request with the exact same json body

I have a SpringBoot application which simply acts as a middleman. It receives an API request in JSON and forwards this to another server S by calling S's API with the exact same body.
I was exploring the solutions and came across a solution which involved the usage of RestTemplate and MultiValueMap. However, since the json body contains objects rather than simple String, I believe I have to create a DTO with corresponding POJO for the solution to work.
May I ask is the above the only solution, or there is a simple way to forward the request over and get back the response?
Even complex and nested JSON objects can be taken into a Map with key as String and value as Object.
I believe you should just use such a map as your request body and transfer the same to another api.
The middleman server can expose a endpoint that accepts a #RequestBody of Object and
HttpServletRequest then use RestTemplate to forward it to the remote server.
The middleman
#RestController
#RequestMapping("/middleman")
public class MiddleManRestController {
private RestTemplate restTemplate;
#PostConstruct
public void init() {
this.restTemplate = new RestTemplate();
this.restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(this.restTemplate.getRequestFactory()));
}
#RequestMapping(value = "/forward", method = RequestMethod.POST)
public ResponseEntity<Object> forward(#RequestBody Object object, HttpServletRequest request) throws RestClientException {
//setup the url and path
final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("Remote server URL").path("EnpointPath");
//add query params from previous request
addQueryParams(request, builder);
//specify the method
final RequestEntity.BodyBuilder requestBuilder = RequestEntity.method(HttpMethod.POST, builder.build().toUri());
//add headers from previous request
addHeaders(request, requestBuilder);
RequestEntity<Object> requestEntity = requestBuilder.body(object);
ParameterizedTypeReference<Object> returnType = new ParameterizedTypeReference<Object>() {};
//forward to the remote server
return this.restTemplate.exchange(requestEntity, returnType);
}
private void addHeaders(HttpServletRequest request, RequestEntity.BodyBuilder requestBuilder) {
Enumeration<String> headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
requestBuilder.header(headerName, headerValue);
}
}
private void addQueryParams(HttpServletRequest request, UriComponentsBuilder builder) {
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap != null) {
parameterMap.forEach((key, value) -> queryParams.addAll(key, Arrays.asList(value)));
}
builder.queryParams(queryParams);
}
}

API call with Java + STS returning "Content type 'application/octet-stream' not supported"

I am working on part of an API, which requires making a call to another external API to retrieve data for one of its functions. The call was returning an HTTP 500 error, with description "Content type 'application/octet-stream' not supported." The call is expected to return a type of 'application/json."
I found that this is because the response received doesn't explicitly specify a content type in its header, even though its content is formatted as JSON, so my API defaulted to assuming it was an octet stream.
The problem is, I'm not sure how to adjust for this. How would I get my API to treat the data it receives from the other API as an application/json even if the other API doesn't specify a content type? Changing the other API to include a contenttype attribute in its response is infeasible.
Code:
The API class:
#RestController
#RequestMapping(path={Constants.API_DISPATCH_PROFILE_CONTEXT_PATH},produces = {MediaType.APPLICATION_JSON_VALUE})
public class GetProfileApi {
#Autowired
private GetProfile GetProfile;
#GetMapping(path = {"/{id}"})
public Mono<GetProfileResponse> getProfile(#Valid #PathVariable String id){
return GetProfile.getDispatchProfile(id);
}
The service calling the external API:
#Autowired
private RestClient restClient;
#Value("${dispatch.api.get_profile}")
private String getDispatchProfileUrl;
#Override
public Mono<GetProfileResponse> getDispatchProfile(String id) {
return Mono.just(id)
.flatMap(aLong -> {
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return restClient.get(getDispatchProfileUrl, headers);
}).flatMap(clientResponse -> {
HttpStatus status = clientResponse.statusCode();
log.info("HTTP Status : {}", status.value());
return clientResponse.bodyToMono(GetProfileClientResponse.class);
// the code does not get past the above line before returning the error
}).map(GetProfileClientResponse -> {
log.debug("Response : {}",GetProfileClientResponse);
String id = GetProfileClientResponse.getId();
log.info("SubscriberResponse Code : {}",id);
return GetProfileResponse.builder()
// builder call to be completed later
.build();
});
}
The GET method for the RestClient:
public <T> Mono<ClientResponse> get(String baseURL, MultiValueMap<String,String> headers){
log.info("Executing REST GET method for URL : {}",baseURL);
WebClient client = WebClient.builder()
.baseUrl(baseURL)
.defaultHeaders(httpHeaders -> httpHeaders.addAll(headers))
.build();
return client.get()
.exchange();
}
One solution I had attempted was setting produces= {MediaType.APPLICATION_JSON_VALUE} in the #RequestMapping of the API to produces= {MediaType.APPLICATION_OCTET_STREAM_VALUE}, but this caused a different error, HTTP 406 Not Acceptable. I found that the server could not give the client the data in a representation that was requested, but I could not figure out how to correct it.
How would I be able to treat the response as JSON successfully even though it does not come with a content type?
Hopefully I have framed my question well enough, I've kinda been thrust into this and I'm still trying to figure out what's going on.
Are u using jackson library or jaxb library for marshalling/unmarshalling?
Try annotating Mono entity class with #XmlRootElement and see what happens.

Categories