JerseyTest framework path encoding replaces ? with %3F - java

I have a failing test, which should be passing. The service works fine, but the JerseyTest JUnit test is failing with status 400.
Using Postman or a browser, when I try this URL against the deployed service:
http://localhost:8080/myService/123?appId=local&userId=jcn
I get correct result, status 200 and see the following in the log:
INFO: 4 * Server has received a request on thread http-nio-8080-exec-5
4 > GET http://localhost:8080/myService/123?appId=local&userId=jcn
Note the ? in the URL, which is correct.
But when I try this unit test in my JeryseyTest-extended Junit class:
#Test
public void getWithCorrectUrlExecutesWithoutError()
{
String x = target("myService/123?appId=local&userId=jcn").request().get(String.class);
}
it fails with a status 400, and I see this in the log:
INFO: 1 * Server has received a request on thread grizzly-http-server-0
1 > GET http://localhost:9998/myService/123%3FappId=local&userId=jcn
Note that the ? has been replaced with %3F.
I don't understand what is happening. If I try the "%3F" URL in the browser, I see the same 400 error from the unit test. So I feel somewhat certain that the encoding of the url is the problem.
Here is my Jersey resource, partial listing because it's kind of long, but I am pretty sure this is the relevant part:
#Component
#Path("/myService")
public class MyResource
{
#Autowired
SomeDao someDao;
#NotBlank
#QueryParam("appId")
private String appId;
#NotBlank
#QueryParam("userId")
private String userId;
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Status getStatus(#NotBlank #PathParam("id") String id)
{
errors = new ArrayList<>();
Status retVal;
if(validateId(id))
{
retVal = someDao.getStatus(id);
}
else
{
throw new BadParameterException(String.join(" | ", errors));
}
return retVal;
}
}

You can use the queryParam method on your WebTarget instance:
String x = target("myService/123")
.queryParam("appId", "local")
.queryParam("userId", "jcn")
.request()
.get(String.class);

Related

Spring web server considers part of uri as path variable during testing

There are two kind of similar endpoints, let's assume:
POST devices/{uuid}/{imei} and POST devices/{uuid}/device-info. The first one is to update IMEI (delivered via path variable) of device specified by UUID and the second one is to update its other parameters (delivered with request as json body).
While server is working "normally" from a jar file, both endpoints works properly how it is described above, which was tested by Postman. But when I run integration tests (with maven or directly through IntelliJ), sending POST request to devices/{uuid}/device-info is interpret on server side as a request to devices/{uuid}/{imei}, where phrase "device-info" is treated as IMEI number.
For integration tests I use autoconfigured MockMvc class and SpringBootTest + Mockito + JUnit4 utilities. webEnvironment is set as SpringBootTest.WebEnvironment.MOCK and everything is ran with SpringRunner.
I was looking for solutions, but actually found nothing. Has anyone met with something similar?
EDIT:
I'm adding API declarations if it can help.
#ResponseStatus(value = HttpStatus.NO_CONTENT, reason = "Device info successfully updated")
#PutMapping(value = "/devices/{deviceUuid}/device-info", consumes = {"application/json"})
ResponseEntity<Void> updateDeviceInfo(#Valid #RequestBody DeviceInfo deviceInfo);
#ResponseStatus(value = HttpStatus.NO_CONTENT, reason = "Device IMEI successfully updated")
#PutMapping(value = "/devices/{deviceUuid}/{imei}")
ResponseEntity<Void> updateDeviceImei(#PathVariable("deviceUuid") UUID deviceUuid, #PathVariable("imei") String imei);
The test itself is as simple as it can be:
DeviceInfo deviceInfo = this.prepareDeviceInfo();
String url = String.format("/v3/devices/%s/device-info", super.firstDeviceUuid);
mvc.perform(put(url)
.content(asJsonString(deviceInfo)))
.andExpect(status().is(204));
where asJsonString is simple helper method to prepare JSON from an object with Jackson methods.
Not sure what is the problem in your case. But I tried this code and it works for me
#RestController
#Slf4j
public class DeviceController {
#ResponseStatus(value = HttpStatus.NO_CONTENT, reason = "Device info successfully updated")
#PutMapping(value = "/devices/{deviceUuid}/device-info", consumes = {"application/json"})
ResponseEntity<Void> updateDeviceInfo(#RequestBody Product product, #PathVariable("deviceUuid") UUID deviceUuid){
log.info("Inside updateDeviceInfo");
return ResponseEntity.ok().build();
};
#ResponseStatus(value = HttpStatus.NO_CONTENT, reason = "Device IMEI successfully updated")
#PutMapping(value = "/devices/{deviceUuid}/{imei}")
ResponseEntity<Void> updateDeviceImei(#PathVariable("deviceUuid") UUID deviceUuid, #PathVariable("imei") String imei){
log.info("Inside updateDeviceInfo");
return ResponseEntity.ok().build();
};
}
For test cases
#SpringBootTest
#AutoConfigureMockMvc
public class DeviceControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#Test
public void test() throws Exception {
Product product = new Product();
String url = String.format("/devices/%s/device-info", UUID.randomUUID().toString());
mvc.perform(put(url)
.content(objectMapper.writeValueAsString(product)))
.andExpect(status().is(204));
}
#Test
public void test2() throws Exception {
Product product = new Product();
String url = String.format("/devices/%s/%s", UUID.randomUUID().toString(),UUID.randomUUID().toString());
mvc.perform(put(url))
.andExpect(status().is(204));
}
}
I've finally found an answer. When I just commented devices/{uuid}/{imei} endpoint handler in controller, test's result status was 415, so it looked like no handler was found in controller. Then I found this solution: Spring MVC testing results in 415 error which worked for me perfectly.
I just set in my test case a content type to MediaType.APPLICATION_JSON_UTF8 as below and thanks to that it was correctly interpret on the server side.
mvc.perform(put(url)
.content(mapper.writeValueAsString(deviceInfo))
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().is(204));
EDIT: MediaType.APPLICATION_JSON works well too.

Request method 'GET' not supported with 'POST' mapping in Spring boot

Hello I'm trying to create a POST method and I keep getting the "404 Request method 'GET' not supported" error. Below I'll post my Rest controller and below that I'll post my service class. The only thing not working is the #PostMapping method.
#RequestMapping("/ATM")
public class ATMController {
private ATMService atmService;
#Autowired
public ATMController(ATMService atmService) {
this.atmService = atmService;
}
#GetMapping(path = "/{id}")
public ATM getATMById(#PathVariable long id){
return atmService.getByID(id);
}
#PostMapping(path = "/{id}/withdraw/{amount}")
public List<Bill> withdrawMoney(#PathVariable long id,#PathVariable float amount){
return atmService.withdrawMoney(id,amount);
}
}
#Service
public class ATMService {
private ATMRepository atmRepository;
private BillRepository billRepository;
#Autowired
public ATMService(ATMRepository atmRepository, BillRepository billRepository) {
this.atmRepository = atmRepository;
this.billRepository = billRepository;
}
public void save(ATM atm) {
atmRepository.save(atm);
}
public ATM getByID(Long id) {
return atmRepository.findById(id).get();
}
public List<Bill> getBillList(Long id) {
return atmRepository.findById(id).get().getBillList();
}
#Transactional
public List<Bill> withdrawMoney(Long id, float amount) {
List<Bill> allBills = getBillList(id);
List<Bill> billsToWithdraw = new ArrayList<>();
float amountTransferred = 0;
for (Bill bill : allBills) {
if (bill.getValue() == 100) {
billsToWithdraw.add(bill);
amountTransferred += bill.getValue();
}
if (amountTransferred == amount) {
for (Bill billToWithdraw : billsToWithdraw) {
billRepository.delete(billToWithdraw);
}
return billsToWithdraw;
}
}
return null;
}
}
I don't see the issue, I've tried switching to #GetMapping and removed the actual transaction "billRepository.delete(billToWithdraw);" and the method then returns the correct bills.
As the error says 404 Request method 'GET' not supported means you are making a GET request instead of POST.
You can make use of tools like Postman to make a post request. Hitting /{id}/withdraw/{amount} via any browser will prompt a GET request and not a POST request.
The issue is that you are sending a GET request to an end point that is configured to accept only POST request. This will probably help you to test them.
How to test
In case you GET requests -
You CAN directly check the api from the browser address bar. Type in the api and hit enter.Its that Simple!
You can use a tool such as Postman, SoapUI, etc to send a GET request.
You could write an html form with action="get mapping uri" and method="GET"
If your API uses any documentation or design tools such as swagger you can test it from its interface.
In case you POST requests -
You CANNOT directly check the api from the browser address bar.
You can use a tool such as Postman, SoapUI to send a POST request.
You could write an html form with action="post mapping uri" and method="POST".
If your API uses any documentation or design tools such as swagger you can test it from its interface.
In my case the problem was that I called https://localhost:8080/my-service but the port 8080 not supports HTTPS so I changed my call to http://localhost:8080 and resolved my problem. However when calling a http with https spring makes internally a GET Request

springtoolsuite rest service not referencing function

I am new to java and trying to implement a rest web service with spring tool suite. I successfully ran an example from a guide and tried to add a POST function to the basic Hello World service. The web service is running using the Spring boot App and all I can trace is that the function is not found. 404 status. Here is code:
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
private static final Logger logger = LoggerFactory.getLogger(RestController.class);
#RequestMapping(value = "/greeting", method = RequestMethod.GET)
public #ResponseBody Greeting greeting(#RequestParam(value="name", defaultValue="World") String name, HttpServletResponse httpResponse_p,
WebRequest request_p) {
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
// #Secured({ "ROLE_USER" })
#RequestMapping(method=RequestMethod.POST, value= {"/addNewPage/{customername}/{streamname}/{name}"})
public Greeting addName(#RequestBody String body, #PathVariable("customername") String customername, #PathVariable("streamname") String streamname,
#PathVariable("name") String name, HttpServletResponse httpResponse_p, WebRequest request_p) {
if (customername.isEmpty() || streamname.isEmpty()) {
String eMessage = "ERROR - NO PARAMETERS INCLUDED!";
httpResponse_p.setStatus(HttpStatus.BAD_REQUEST.value());
return new Greeting (counter.incrementAndGet(), String.format(template, "BAD PARAMETERS"));
}
return new Greeting(counter.incrementAndGet(), String.format("WORKING - ADDED " + name));
}
So if I paste the following in my browser:
http://localhost:8080/greeting?name=Al
I get the following correct response:
{"id":2,"content":"Hello, Al!"}
But if I try
http://localhost:8080/addNewPage/something/stream1/ABC
I get the following:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing
this as a fallback.
Tue Mar 24 17:19:29 EDT 2015
There was an unexpected error (type=Not Found, status=404).
No message available
could someone see what I am missing here? Or be so kind to suggest a good step by step tutorial that goes through the following functions GET/POST/PUT/DELETE?
When you paste the url in the browser you are doing a GET. Your mapping is for POST so a 404 error is what expected.
Normally when you POSTing you should have some data in the request body but anyway just for testing you can use curl to send post requests.
Here is a tutorial on how to use it for testing rest apis

post service is not calling throwing 400 (Bad Request)

Hi friends I am using Angularjs and rest-servies but when I am calling rest services from service.js file something is goning wrong and it is throwing 400(bad request )
main.js
garantiesService.getTarifs($scope.recap.ageDirigeant,$scope.selectedCompany.zipcode)
.success(function(){
console.log('in success');
})
service.js
healthApp.factory('garantiesService', ['$http', function($http) {
var service = {
getTarifs: function(age,zipcode)
{
console.log("age : "+age);
console.log("zipcode : "+zipcode);
var directorHealthInsuranceInfo = {};
directorHealthInsuranceInfo.age=age;
directorHealthInsuranceInfo.department=zipcode;
return $http.post('rest-service/quotes/health /director',directorHealthInsuranceInfo);
}
};
return service;
HealthInsuranceController.java
#Controller
public class HealthInsuranceQuoteResource {
#RequestMapping("quotes/health/director")
#ResponseBody
public String quoteDirector(#RequestBody DirectorHealthInsuranceInfo info) {
System.out.println("------HealthInsuranceQuoteResult------");
return "hi";
}
DirectorHealthInsuranceInfo.java
#Value
public class DirectorHealthInsuranceInfo {
private String department;
private int age;
}
when I am sending the request it is throwing Bad Request 400 error.
I see that there is a space in the url you supplied to the http.post method.
"rest-service/quotes/health /director"
I don't know if that is causing it.
But I also see that you POST your request to the service. Are you sure that your endpoint has been set up for POST requests?
I would recommend creating a basic endpoint that you call with a GET request, and no parameters. Just to root out the problem.

Jersey Test Framework - define default error response for all unknown paths in grizzly

To test our API that connects to the facebook graph API we use a mock server setup based on Jersey Test Framework and grizzly:
#Path("/" + PostRest.RESOURCE)
#Produces("application/json")
public class PostRest {
public static final String RESOURCE = "111_222";
#GET
public Response getPost(#QueryParam("access_token") String access_token) {
if (access_token != VALID_TOKEN) {
return Response.status(400).entity(createErrorJson()).build();
}
return Response.status(200).entity(createSomeJsonString()).build();
}
Now while I can react to an invalid or missing access_token with the correct error response, I also want to test that my API reacts correctly when trying to access an unkown resource at facebook ie an unkown path.
Right now I get a 404 from my grizzly obviously, if I try to access say "/111_2", but facebook seems to catch that error and wrap it inside a Json response, containing the string "false" with status 200.
So... How do I set up the Test Framework to return
Response.status(200).entity("false").build();
every time it is called for an known path?
Basic example:
#ContextConfiguration({ "classpath:context-test.xml" })
#RunWith(SpringJUnit4ClassRunner.class)
public class SomeTest extends JerseyTest {
#Inject
private SomeConnection connection;
private String unkownId = "something";
public SomeTest() throws Exception {
super("jsonp", "", "com.packagename.something");
}
#Test(expected = NotFoundException.class)
public void testUnkownObjectResponse() throws NotFoundException {
// here it should NOT result in a 404 but a JSON wrapped error response
// which will be handled by the Connection class and
// result in a custom exception
connection.getObject(unkownId);
}
Or maybe I can set up grizzly to behave as desired..?!
Thanks!
Obviously facebook has it own service to intercept errors. Same thing should be done in your code. Just expose you own test service that intercepts all request
#Path("/test/errorTrap")
public class ErrorTrapService{
....
}
This service will produce any response you want. So any un-existing pages like http://mytest/test/errorTrap/111_2 will be intercepted by test service and produce expected response for you

Categories

Resources