Test of endpoint that sends a request to external api - java

There is a small method mymethod that gets the response from the external api data in json format, which will be Dto and
then there is a getEnums method that selects which data to leave and returns a list of strings and as a result mymethod itself returns a list of strings.
I tried to write a test, but I get :
Expected :200
Actual :302
As I understand the problem is in the redirection, but how to solve it I do not understand, maybe you have an answer?
controller
#GetMapping(value = "api/mymethod", produces = "application/json")
public ResponseEntity<List<String>> mymethod(#RequestHeader(value = "Enum") String Enum,
#RequestParam(value = "Type") String Type) {
URI uri = UriComponentsBuilder.fromUriString("...url...").build().toUri();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(null, headers);
ResponseEntity<myDto> response =
restTemplate.exchange(uri, HttpMethod.GET, entity, myDto.class);
List<String> currencies =
getEnums(response, Type); // <- custom method
return new ResponseEntity<>(currencies, HttpStatus.OK);
}
// (The test is not written in full, left exactly the problem area)
test
#Test
public void mytest() throws Exception{
ResultActions getResult = mockMvc.perform(get("/api/mymethod")
.header("Enum", "OOL")
.param("Type", "Counter"))
.andExpect(status().isOk());
}

The problem with testing against an external service is that you do not manage it's state. Therefore your test cases may show varying results even if you did not change anything.
Usually you'd create a mock of the REST api your test object would access. Then you can send requests to your test object and check in the mocked api whether the expected requests did come in. You can also fake success or error responses and check how your test object reacts.
To finally answer your question: How do you want your client to treat a redirection? Should it follow or error out? Looking at the meaning of status 302 it means the resource has moved temporarily or at least was found at a new location. This might mean a valid redirect if the server is trying to loadbalance or tries to point out a url that is closer to you network-wise. Therefore I believe the client should follow a 302.

Related

Jersey HTTP Delete,Put Response Status: 405 (Method Not Allowed)

Day 1: Added below rest endpoint for delete operation.
#Path("/company/v1/department")
#Component
public class ManageResource {
#DELETE
#Path("/{identifier}/{identifier_value}/employee")
public void delete(#PathParam("identifier") String identifier,
#PathParam("identifier_value") final String identifierValue,
#QueryParam("age") final String age) {
//delete operation
}
}
I was able to invoke DELETE endpoint using postman with below request:
DELETE: http://localhost:8080/company/v1/department/name/baner/employee?age=50
Day 2: Added below rest endpoint for the update operation in the same resource.
#Path("/company/v1/department")
#Component
public class ManageResource {
#DELETE
#Path("/{identifier}/{identifier_value}/employee")
public void delete(#PathParam("identifier") String identifier,
#PathParam("identifier_value") final String identifierValue,
#QueryParam("age") final String age) {
//delete operation
}
#PUT
#Path("/empid/{value}/employee")
#Consumes(MediaType.APPLICATION_JSON)
public void update(#PathParam("value") final String identifierValue,
#RequestBody final EmployeeUpdateRequest request) {
//update operation
}
}
After adding this new endpoint, I am able to invoke PUT using postman with below request:
PUT: http://localhost:8080/company/v1/department/empid/epid-123/employee
{
//Json request body
}
But when I try to invoke Delete endpoint it is giving me 405 (Method Not Allowed) error.
If I comment my new Put method, then the Delete method works fine.
Also, if I replace Path for Put method to "/{identifier}/{identifier_value}/employee" then both Delete and Put method works fine.
I am using Jersey 1.19 with tomcat.
Can someone help me with this?
Your Paths are in conflict with each other. Let me try to explain:
DELETE = /{identifier}/{identifier_value}/employee
PUT = /empid/{value}/employee
That means when we evaluate the path from left to right, we can either have
{identifier} which is anything or
"empid" which is a fixed string
Jersey always tries to find the "most perfect" match for a REST endpoint. It does so by evaluating the path from left to right.
Fixed strings always take precedence before random variables!
Basically that means when you want to call a DELETE, you cannot have the value "empid" for the variable "{identifier}" because then you are already out-of-scope
So the DELETE call to
http://localhost:8080/company/v1/department/empid/empid-123/employee
will not work as Jersey had to make a decision whether "empid" in the request matches "{identifier}" (DELETE) or "empid" (PUT). And as i tried to explain above, fixed strings take a higher priority.
In contrast, any other DELETE request where
http://localhost:8080/company/v1/department/{identifier}/empid-123/employee
and
{identifier} != "empid"
works.
Possible solution:
make your rest endpoints resource-oriented
DELETE:
/employee/{employee-id}
PUT:
/employee/{employee-id}
Notice how the endpoints are identical, since other than the ID in most systems, no information is needed to identify an entity.

How i can pass a list<String> to the server with url

I am trying to pass the List of String from one server to the another server in spring boot.
How i can get that list at the another server?
The code i have tried-
public void addNewMostPopular(List<String> totalList){
try {
HttpHeaders httpHeaders = getHttpHeaders();
HttpEntity<String> httpEntity = new HttpEntity<String>(null, httpHeaders);
ResponseEntity responseEntity = restTemplate.exchange(
BASE_URL + "addMostPopular/"+new ArrayList<>(totalList), HttpMethod.POST, httpEntity,TrendingCategoryDTO.class);
}
and at server side i tried to get like-
#RequestMapping(value="/addMostPopular/[{totalList}]", method = RequestMethod.POST)
public void addMostPopularProduct( #PathVariable List<String> totalList) {}
Past long object in the url is a bad praxis, thats because spring url interpreter has a maximun lenght, so if you pass more than 2048 or 4096 char in some cases your request will return Response 400 bad request and won't execute anycode on your spring server.
After this aclaration, is there any option to pass a list? Yes, of course! But we need use #RequestBodylike this:
#PostMapping("/addMostPopular")
public void addMostPopularProduct(#RequestBody List<String> totalList) {
// Your function
}
Now we need to add to our other server the List we want to pass to this request in the body of the request.
If you like to pass a List of values in the url one possibility is to pass them as url parameters.
You have to create a link similar to the followings:
http://youserver/youraction?param=first&param=second&param=third
or
http://youserver/youraction?param=first,second,third
Your controller in spring must be something like
#Controller
public class MyController {
#GetMapping("/youraction")
public String yourAction(#RequestParam("param") List<String> params) {
// Here params is tre list with the values first, second, third
}
}
This action is able to handle both kind of requests that I wrote before.
There are many ways to pass infomation between servers.
The simple way is to initiate an http request, based on your request method get or post put the parameters to the appropriate location : reuqest header or request body. You can do like #Davide Lorenzo MARINO.
Or use a message queue, like ActiveMq.
In the case of the same registry center, you can also use #feign to resolve it.

Pass parameters in the client

I use a RESTful Web Service. In this web service I must pass a bean that I want to save as a parameter.
Here is the server code:
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Unidade inserir(Unidade unidade){
Session s = ConnectDb.getSession();
try {
s.getTransaction().begin();
s.save(unidade);
s.getTransaction().commit();
return unidade;
} catch (Exception e) {
e.printStackTrace();
s.getTransaction().rollback();
return null;
} finally {
s.close();
}
}
I have the following code in the client:
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource webResource = client.resource("http://localhost:8080/RestauranteWeb/rest/unidades/7");
Builder builder = webResource.accept(MediaType.APPLICATION_JSON);
GenericType<Unidade> genericType = new GenericType<Unidade>() {};
Unidade u = new Unidade();
u.setUnidSigla("KG");
//How to pass this bean as parameter?
Unidade response = builder.post(genericType);
System.out.println(response);
How can I pass the bean to the method as a parameter?
Using Jackson as a Serializer/DeSerializer
If your Unidade object is annotated with Jackson and/or a Deserializer is registered then you should be able to POST with a BODY that contains the JSON representing the Unidade object. It should be magically deserialized and rebuilt as an object on the server side.
Important
Make sure that you add a Content-Type header in the POST request with a value of application/json. Without this header your Jersey might not know what to do with the body.
You would use the Jackson ObjectMapper to serialize your Unidade object to JSON and send that instead of whatever that GenericType stuff is.
I have both Jersey and RESTEasy implementations that work seamlessly with Jackson in this manner.
How can I pass the bean to the method as a parameter?
Checkout the documentation for the post method:
/**
* Invoke the POST method with a request entity that returns a response.
*
* #param <T> the type of the response.
* #param c the type of the returned response.
* #param requestEntity the request entity.
* #return an instance of type <code>c</code>.
* #throws UniformInterfaceException if the status of the HTTP response is
* greater than or equal to 300 and <code>c</code> is not the type
* {#link ClientResponse}.
* #throws ClientHandlerException if the client handler fails to process
* the request or response.
*/
<T> T post(Class<T> c, Object requestEntity)
throws UniformInterfaceException, ClientHandlerException;
The method takes two parameters. First parameter is the expected response type, and second one is the entity which is going to be put in the request body.
What happens here, when performing the request Jersey would serialize the object passed as a request entity into the JSON string (hence you've set the header - accept(MediaType.APPLICATION_JSON)). When the response from the server arrives, Jersey will deserialize it (the inverted process as in case of requestEntity) and return you the object.
And what if my method receives more than 1 parameter? Because the post
method only acepts 1
Well you cannot do it with JAX-RS, and it makes little sense actually. You can pass multiple parameters to the method as #PathParam or a #MatrixParam, but there can be only one associated with the body (well you have only one body in our request, right?). Checkout answer to this question and checkout how to use #PathParam or #MatrixParam
Let's suppose instead of returning a "Unidade" class, my method
returns a String. So, it will receive a "Unidade" as parameter and
return a "String". How can i retrieve it in this case, passing the
"Unidade" instance as before?
I believe you could achieve that with post(String.class, unidadeInstance). The first parameter doesn't have to be the same as the second. It's valid to accept one parameter and return another. It is even valid to take a parameter and return nothing in the body (like you have done it in the code attached to your question). You could accept the body and send back response containing status 201 Created and Location header entry pointing to the URL of the newly created resource.
Not sure what's your purpose for GenericType. Anyway, try the code below.
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
Unidade u = new Unidade();
u.setUnidSigla("KG");
WebResource webResource = client.resource("http://localhost:8080/RestauranteWeb/rest/unidades/7");
Unidade response = webResource.accept(MediaType.APPLICATION_JSON)
.type(MediaType.APPLICATION_JSON)
.post(Unidade.class, u);
I'm not sure if it helps but i had a similar problem.
My scenario was i need a webservice which had to receiver a bunch of values which are organized as a kind of profile. But this service has to handle that there are comming more profiles where still old clients using the service. The interface had to be as static as possible.
Our solution is very simple. We only post a single text field as content of the post. But this includes the serialized state of an profile object in JSON.
Pseudo code:
public class Profile1 {
...
public String asJSON() {
JSONObject obj = new JSONObject();
obj.put("profileAtr1", profileAtr1);
...
return obj.toString()
}
}
formParams.put("profile", profile.asJSON());
client.post(formParams);
This way it's not automaticly deserialized but it's easy to do it by hand.
We do this with a generic Profile object which can be created out of JSON String in the constructor.
Pseudo code:
public GenericProfile {
public GenericProfile(String json) {
JSONObject obj = new JSONObject(json);
String profileName = obj.getString("profileName");
if (profileName.equals("Profile1") {
this = new Profile1(obj); // I know this is not working ;) My impl. is a litle bit more complecated as that. I think i use a static method in the generic profile to create an instance i need.
} ...
}
}
And then in your webservice only have this one form param to handle and to deserialize ;)
Pseudo code:
public ResponseEnvelope coolServiceFunction(#FormParam("profile") String profileData) {
GenericProfile profile = new GenericProfile(profileData);
if (profile instanceof Profile1) {
do what you want
}
}
Sorry for the pseudo code, but i had alleady shutdown my dev vm and have no access to any repository any more :(
I think the biggest benefits from this solution is:
1. It can transport anything you can pack in JSON. I transfer BASE64 encoded binary blocks and heavyly encrypted textdata this way.
2. The easiest tutorial example of your REST Framework of the POST Service will provide all you need to do this.
3. You can be sure that your interface will stay for a long period of time.
Hope that helps

Returning 200 response code instead of 204

This is my method for creating Response with header parameters and body:
public Response sendOKResponse(request req)
{
ResponseBuilderImpl builder = new ResponseBuilderImpl();
// set the header params.
for(int index =0; index<req.headerParameters.size(); index++)
{
builder.header(req.headerParameters.get(index).getName(), req.headerParameters.get(index).getBody());
}
// set the body and response code
builder.status(Response.Status.OK).entity(req.getBody());
Response r = builder.build();
return r;
}
And this is how i return the Response:
Response response;
response = sendBadMesseage();
return response;
This code returns code 204(No content) instead of 200.
Any ideas why?
You shouldn't be instantiating your response builder with new, the whole point of the JAX-RS abstraction layer is to hide implementation details away from calling clients. This is what makes it possible to have various vendor implementations which can be interchanged at will. Also, if you are using JEE6, or hope to migrate to it, this code will almost certainly fail. Most JEE6 vendor implementations utilize CDI, which is concept-incompatible with usage of new. But, closer to the topic, the JAX-RS implementation specifies that a 204 status code be returned if a responses wrapped entity is null. You might want to verify this is not the case in any of your methods. Also, you might want to make some changes to your code:
public Response sendOKResponse(request req) {
ResponseBuilder response = Response.ok();
// set the header params.
for(Header h: req.headerParameters()) {
builder = builder.header(h.getName(), h.getValue());
}
// set the body and response code
builder = builder.entity(req.getBody());
return builder.build();
}
Your sendBadMessage method should also look similar to above. You can log your entity before adding it to the builder, to verify that you only get a 204 when it's null.

Spring ResponseEntity

I have a use case where I need to return a PDF to a user which is generated for us. It seems that what I need to do is utilize the ResponseEntity in this case, but I have a couple of things which are not very clear.
How can I redirect the user -- let's pretend they don't have the permissions to access this page? How can I redirect them to a separate controller?
Am I able to set the response encoding?
Can I achieve either of these two without bringing in the HttpResponse as a parameter to my RequestMapping?
I'm using Spring 3.0.5. Example code below:
#Controller
#RequestMapping("/generate/data/pdf.xhtml")
public class PdfController {
#RequestMapping
public ResponseEntity<byte []> generatePdf(#RequestAttribute("key") Key itemKey) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.valueOf("application/pdf"));
if (itemKey == null || !allowedToViewPdf(itemKey)) {
//How can I redirect here?
}
//How can I set the response content type to UTF_8 -- I need this
//for a separate controller
return new ResponseEntity<byte []>(PdfGenerator.generateFromKey(itemKey),
responseHeaders,
HttpStatus.CREATED);
}
I'd really like to not pull in the Response... None of my controllers have done so thus far, and I'd hate to have to bring it in at all.
Note, this works in Spring 3.1, not sure about spring 3.0.5 as asked in the original question.
In your return ResponseEntity statement where you want to handle the redirect, just add in a "Location" header to the ResponseEntity, set the body to null and set the HttpStatus to FOUND (302).
HttpHeaders headers = new HttpHeaders();
headers.add("Location", "http://stackoverflow.com");
return new ResponseEntity<byte []>(null,headers,HttpStatus.FOUND);
This will keep you from having to change the return type of the controller method.
Regarding the redirect, all you need to do is change the return type to Object:
#Controller
#RequestMapping("/generate/data/pdf.xhtml")
public class PdfController {
#RequestMapping
public Object generatePdf(#RequestAttribute("key") Key itemKey) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.valueOf("application/pdf"));
if (itemKey == null || !allowedToViewPdf(itemKey)) {
return "redirect:/some/path/to/redirect"
}
//How can I set the response content type to UTF_8 -- I need this
//for a separate controller
return new ResponseEntity<byte []>(PdfGenerator.generateFromKey(itemKey),
responseHeaders,
HttpStatus.CREATED);
}
Redirects are easy - for your handler method's return String, just prepend with redirect:, as in return "redirect:somewhere else".
Not sure why you're objecting to the Response object. Is there a reason? Otherwise, if you just stream the PDF as an OutputStream on the HttpServletResponse object, then you don't actually need to return the PDF from your handler method - you just need to set the PDF stream on the response, which you can add to your handler method's signature. See http://www.exampledepot.com/egs/javax.servlet/GetImage.html for an example.
Instead of dealing with redirecting (these are instances which we open in new windows / tabs) anyhow we decided to just display the error message they would have received.
This likely won't work for all, but with the way we add error / status messages we were unable to get those messages to persist on the view upon exception occurring.

Categories