WebClientRequestException when wiremocking Spring Boot Service POST method - java

I'm testing my Service in Spring Boot, but I'm getting WebClientRequestException: failed to resolve 'null' after 6 queries; nested exception is java.net.UnknownHostException: failed to resolve 'null' after 6 queries.
I'm new to mocking and testing in general. I guess stubbing is properly done, but asserting is done wrong. Here's my service:
public String changeState(String ids) {
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("ids", ids);
return webClient.post()
.uri("/vobjects/ecotox_study__c/actions/Objectlifecyclestateuseraction.ecotox_study__c.study_setup_completed_state__c.change_state_to_study_contracted_useract__c")
.header(HttpHeaders.AUTHORIZATION, getSessionId())
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.bodyValue(form)
.retrieve()
.bodyToMono(String.class)
.block();
}
And here's my test, with the response from Postman as a body:
#Test
#DisplayName("Test changeState")
void changeState() {
String body = "{\n" +
" \"responseStatus\": \"SUCCESS\",\n" +
" \"data\": [\n" +
" {\n" +
" \"responseStatus\": \"SUCCESS\",\n" +
" \"id\": \"V4600000001K001\",\n" +
" }\n" +
" ]\n" +
"}";
wireMockServer.stubFor(post(urlEqualTo("/api/v21.3/vobjects/ecotox_study__c/actions/Objectlifecyclestateuseraction.ecotox_study__c.study_setup_completed_state__c.change_state_to_study_contracted_useract__c"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(body))
);
String vaultIds = vaultServiceTest.changeState("V4600000001K001");
assertEquals(body, vaultIds);
}
As I can see, asserting is obviously done wrong, because I'm asserting the whole body vs. just a single string. I'm not sure is this the right approach or my test method is wrong as a whole.

WebClientRequestException: failed to resolve 'null' after 6 queries;
This error message suggests that the WebClient is being created with a null base URL when the test is run. Not sure how the WebClient is being created in the actual code, but one approach that could also make this work in the test would be to create one in the test class with localhost as the base URL so that it will work with WireMock.
For example:
#Test
#DisplayName("Test changeState")
void changeState() {
//Create and start the WireMockServer for localhost on port 8090
WireMockServer wireMockServer = new WireMockServer(8090);
wireMockServer.start();
//Create a WebClient with base URL localhost on port 8090
WebClient localhostWebClient = WebClient.builder().baseUrl("http://localhost:8090").build();
//Initialize VaultService with the localhost web client
VaultService vaultService = new VaultService(localhostWebClient);
...
To allow the WebClient to be injected into the Vault service, add a parameter for WebClient in the constructor:
public class VaultService {
private WebClient webClient;
public VaultService(WebClient webClient) {
this.webClient = webClient;
}
...
As I can see, asserting is obviously done wrong, because I'm asserting the whole body vs. just a single string.
The JSON response can be mapped to a Java object assuming the field names and data types match up, and the JSON is syntactically valid. Here is some Java objects that could be made that matches the data model of the JSON response:
VaultResponse.java:
public class VaultResponse {
private String responseStatus;
private VaultResponseData[] data;
public String getResponseStatus() {
return responseStatus;
}
public void setResponseStatus(String responseStatus) {
this.responseStatus = responseStatus;
}
public VaultResponseData[] getData() {
return data;
}
public void setData(VaultResponseData[] data) {
this.data = data;
}
}
VaultResponseData.java:
public class VaultResponseData {
private String responseStatus;
private String id;
public String getResponseStatus() {
return responseStatus;
}
public void setResponseStatus(String responseStatus) {
this.responseStatus = responseStatus;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Now in the changeState() method, the response can be mapped to a VaultResponse object instead of a String:
return webClient.post()
.uri("/vobjects/ecotox_study__c/actions/Objectlifecyclestateuseraction.ecotox_study__c.study_setup_completed_state__c.change_state_to_study_contracted_useract__c")
.header(HttpHeaders.AUTHORIZATION, getSessionId())
.header(HttpHeaders.ACCEPT, PageAttributes.MediaType.APPLICATION_JSON_VALUE)
.bodyValue(form)
.retrieve()
.bodyToMono(VaultResponse.class) //Response can be mapped to an Object instead of a raw String
.block();
And finally, in the assert, you can assert on just the ID string instead of the whole JSON response body. A couple of things have to be fixed as well:
There is an extra comma after the ID in the JSON body. \"id\": \"V4600000001K001\",\n" should become \"id\": \"V4600000001K001\"\n"
The WireMock URL should match the URL being used in the code.
Here is the whole test method after all of the changes:
#Test
#DisplayName("Test changeState")
void changeState() {
//Create and start the WireMockServer for localhost on port 8090
WireMockServer wireMockServer = new WireMockServer(8090);
wireMockServer.start();
//Create a WebClient with base URL localhost on port 8090
WebClient localhostWebClient = WebClient.builder().baseUrl("http://localhost:8090").build();
//Initialize VaultService with the localhost web client
VaultService vaultService = new VaultService(localhostWebClient);
String body = "{\n" +
" \"responseStatus\": \"SUCCESS\",\n" +
" \"data\": [\n" +
" {\n" +
" \"responseStatus\": \"SUCCESS\",\n" +
" \"id\": \"V4600000001K001\"\n" + //Extra comma here was removed to make this valid JSON
" }\n" +
" ]\n" +
"}";
//This URL must match the URL being called in the code
wireMockServer.stubFor(post(urlEqualTo("/vobjects/ecotox_study__c/actions/Objectlifecyclestateuseraction.ecotox_study__c.study_setup_completed_state__c.change_state_to_study_contracted_useract__c"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(body))
);
VaultResponse vaultResponse = vaultService.changeState("V4600000001K001");
assertEquals("V4600000001K001", vaultResponse.getData()[0].getId());
}

Related

Java Exception: Unauthorized: 401

I'm getting the error: Exception: org.springframework.web.client.HttpClientErrorException$Unauthorized: 401, when trying to connect to Jira through HttpHeader, and the credentials are configured in a configserver file, which would be this:
#Component
public class JiraHttpHeadersHelper {
#Value("${first.jira.auth.user}")
private String firstJiraAuthUser;
#Value("${first.jira.auth.psw}")
private String firstJiraAuthPsw;
public HttpHeaders jiraHeadersWithAuthentication() {
String plainCreds = firstJiraAuthUser + ":" + firstJiraAuthPsw;
System.out.println("Credenciales JiraServices: "+plainCreds);
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCreds.getBytes());
String base64Creds = new String(base64CredsBytes);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic" + base64Creds);
headers.setContentType(MediaType.APPLICATION_JSON);
System.out.println("Authorization JiraServices: "+headers);
return headers;
}
}
And the method where I command to call the file above and where I get the error on the line ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.GET,this.requestEnt, String.class);, would be this:
public ResponseEntity<String> getPriorityJira() {
//Request entity created
this.requestEnt = new HttpEntity(this.jiraHttpHeadersHelper.jiraHeadersWithAuthentication());
String jql = "priority";
String url = jiraBaseURL + jql;
try {
ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.GET,this.requestEnt, String.class);
System.out.println("HttpStatus"+HttpStatus.OK);
if (result.getStatusCode() == HttpStatus.OK) {
return result;
} else {
logger.error("Jira Generic User maybe blocked, status from API: " +result.getStatusCode() + ". Body: "+ result.getBody());
return new ResponseEntity<>(result.getBody(), result.getStatusCode());
}
} catch(HttpClientErrorException e) {
logger.error("Error getting priorityJira. Exception: "+ e);
return new ResponseEntity<>(e.getStatusCode());
}
}
In fact, when I run the debug and check the credentials, it brings them up without a problem. I've already searched, tried most of the links on this page and I can't find the solution.
Any help would be appreciated in this case, thanks in advance.
When you define your authorization header you concat your key with « Basic » without adding a white space.
headers.add("Authorization", "Basic" + base64Creds);
Instead of :
headers.add("Authorization", "Basic " + base64Creds);
Maybe it’s just that.
Edit :
The answer was to add StandardCharsets.UTF-8 to the String constructor.

MockRestServiceServer test sending a file upload through Multipart-formdata

I have a method that sends a rest request to an api with multipart-formdata, this will upload a file to the external api. However, I am not able to finish the unit test method for this.
The first problem I am finding is that the content-type that I am expecting is always different than the one that method create. For some reason when sending the request the mediatype is multipart-formdata but the header is set as that in addition to charset and boundary. The latter, boundary, is always changing its value therefore I can not set the expected value on the unit tests because it will always be different.
Apart from that, how do I also expect that the content of the request is the same content that I initiated the test with? How do I assert that the payload is the same.
Please check the code:
Service class:
#Service
#Slf4j
public class JiraService {
private HttpHeaders createRequestHeaders(JiraClient jiraClient, MediaType contenType) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(contenType);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setBasicAuth(jiraClient.getUsername(), jiraClient.getPassword());
return headers;
}
private <EC, RC> ResponseEntity<RC> createRequestAndSend(HttpMethod method, String url, HttpHeaders headers,
EC payload, Class<RC> responseType) {
HttpEntity<EC> requestEntity = new HttpEntity<>(payload, headers);
ResponseEntity<RC> responseEntity = restTemplate.exchange(url, method, requestEntity, responseType);
// TODO deal with response
log.error("Loggin something");
return responseEntity;
}
public void addAttachment(JiraClient jiraClient, JiraIssue jiraIssue, JiraAttachment jiraAttachment)
throws MalformedURLException, IOException {
String url = jiraClient.getHost() + "/rest/api/2/issue/" + jiraIssue.getKey() + "/attachments";
HttpHeaders headers = createRequestHeaders(jiraClient, MediaType.MULTIPART_FORM_DATA); // What to do here?
headers.set("X-Atlassian-Token", "no-check");
FileSystemResource file = jiraAttachment.downloadFileFromWeb();
MultiValueMap<String, Object> payload = new LinkedMultiValueMap<>();
payload.add("file", file);
createRequestAndSend(HttpMethod.POST, url, headers, payload, String.class);
jiraAttachment.deleteFileFromSystem();
}
}
ServiceTest.class
#ActiveProfiles("test")
#RestClientTest(JiraService.class)
public class JiraServiceTest {
#Value("classpath:jira/add_attachment/validJiraAttachmentAddition.json")
private Resource validJiraAttachmentAddition;
#Autowired
private MockRestServiceServer server;
#Autowired
private JiraService jiraService;
#Mock
private JiraAttachment mockJiraAttachment;
private FileSystemResource attachmentFileSystemResource;
#BeforeEach
public void setupTests() throws IOException {
// initialize mocks
}
#Test
public void addAttachment_WithValidData_ShouldAddAttachmentToJiraIssue() throws Exception {
String url = host + "/rest/api/2/issue/" + issueKey + "/attachments";
ResponseActions stub = createServiceStub(HttpMethod.POST, url, MediaType.MULTIPART_FORM_DATA_VALUE);
stub = stub.andExpect(header("X-Atlassian-Token", "no-check"));
stub.andRespond(withSuccess());
// How to assert that the content of the request is the same as the resource?
when(mockJiraAttachment.downloadFileFromWeb()).thenReturn(attachmentFileSystemResource);
jiraService.addAttachment(mockJiraClient, mockJiraIssue, mockJiraAttachment);
}
private ResponseActions createServiceStub(HttpMethod method, String url, String contenType) {
String encodedCredentials = Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
ResponseActions stub = server.expect(ExpectedCount.once(), requestTo(url));
stub = stub.andExpect(method(method));
stub = stub.andExpect(header("Content-Type", contenType)); // How to expect the content type here ?
stub = stub.andExpect(header("Authorization", "Basic " + encodedCredentials));
return stub;
}
}
Use ContentRequestMatchers.contentTypeCompatibleWith(MediaType contentType)
import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
...
stub.andExpect(content().contentTypeCompatibleWith(MediaType.MULTIPART_FORM_DATA))

Pattern.List validation regexp fails

Got myself stuck in following problem: previously I had a single regexp to check MSISDN and it looked like this: (380)[0-9]{9}. It worked like a charm but change request came in and I had to split it into two parts - first to check if the string start with 380 and the second one to check if it contains only digits and contains 12 characters. For some reason it doesn't work and fails during test on totally valid MSISDN: 380671112233. I've tried googling thing like regexp which check strating characters ^ but all my experiments failed. Can anybody suggest what am I doing wrong ? Thanks in advance!
#Slf4j
#Validated
#RestController
#RequiredArgsConstructor
#RequestMapping(InfoController.INFO_ENDPOINT )
public class InfoController {
public static final String INFO_ENDPOINT = "/info";
private final InfoService service;
#GetMapping(value = "/{msisdn}")
#ResponseBody
public InfoResponse getInfo(
#Pattern.List({
#Pattern(regexp = "(380)", message = "MSISDN_INCORRECT"),
#Pattern(regexp = "[0-9]{12}", message = "MSISDN_OUT_OF_RANGE")
})
#PathVariable("msisdn") String msisdn,
#RequestParam("cid") String cid) {
log.info("[{}] get information for msisdn {}", cid, msisdn);
InfoResponse response = service.getInfo(msisdn, cid);
log.info("[{}] successful response for msisdn {} - {}", cid, msisdn, response);
return response;
}
}
My test setup
private static final String CID = "123-aaa-bbb-312";
public static final String MSISDN = "380671122333";
public static final String NAME = "PP AA";
public static final long ID = 476561L;
public static final int DISTANCE = 69;
#Autowired
private MockMvc mockMvc;
#MockBean
private InfoService service;
#Test
#DisplayName("Valid request. Expected: valid response")
public void testController1() throws Exception {
InfoResponse response = new InfoResponse(MSISDN, NAME, ID, DISTANCE);
when(service.getInfo(any(), any())).thenReturn(response);
String request = UriComponentsBuilder
.fromPath(InfoController.INFO_ENDPOINT)
.pathSegment(MSISDN)
.queryParam("cid", CID)
.build().toString();
mockMvc.perform(get(request))
.andDo(print())
.andExpect(content().json(
"{\"param1\":\"" + MSISDN + "\"" +
",\"param2\":\"" + NAME + "\"" +
",\"param3\":" + ID +
",\"param4\":" + DISTANCE + "}"
))
.andExpect(status().isOk());
}

Spring MVC: Complex object as parameter

I started to learn spring boot and I'm faced with problems. I have following code:
#RestController
public class UserController {
#RequestMapping("/")
public String getMessageInfo(Message message) {
return "Id is " + message.getId() + ", message is " + message.getMessage() + ", parameter good is " + message.isGood();
}
}
Class Message:
public class Message {
private String message;
private int id;
private boolean good;
public Message() {}
public Message(int id) {this.id = id;}
public Message(String message) {this.message = message;}
public Message(boolean good) {this.good = good;}
public Message(String message, boolean good, int id) {
this.message = message;
this.good = good;
this.id = id;
}
public String getMessage() {
return message;
}
public int getId() {
return id;
}
public boolean isGood() {
return good;
}
}
And when I try to do something like this:
RestTemplate request = new RestTemplate();
String info = request.getForObject("http://localhost:8080/?id=4", String.class);
value of id is ignored. Same problem appears when I send request with boolean good parameter (for example localhost:8080/?good=true). It is called the default constructor instead of Message(boolean)/Message(int). But when I do something like localhost:8080/?message=1234 it isn't ignored. Can you explain me what is the problem?
And one more question: can I send instance of class Message to getMessageInfo in different way than localhost:8080/?message=1234&good=true&id=145? If I have more than 3 parameters? For example if class Message has 100 parameters?
since you are trying to deal with a complex object accept your object from a post request.
#RequestMapping("/",method=RequestMethod.POST)
public String getMessageInfo(#RequestBody Message message) {
return message;
}
in the above code i'm setting method attribute to POST then it will be called when you are making a POST request, and i am using #RequestBody Message message inside the method parameter. which will convert and form an Message object from the incoming request, if you dont put #requestBody annotation then a Bean will be injected to the method by spring instead of forming a one from the request.
you can try this code to make the request
final String uri = "http://localhost:8080/";
Message message = new Message(1, "Adam",true);
RestTemplate restTemplate = new RestTemplate();
Message result = restTemplate.postForObject( uri, message, Message.class);
when making an request create an Message object setting each and every field in it, otherwise you will end up in having Bad request error.
I solved the problem, if add smth like this:
#ModelAttribute("message")
public Message getMessage(String message, boolean good, int id){
return new Message(message, good, id);
}
#RequestMapping("/")
public String getUserInfo(#ModelAttribute("message") Message message) {
return "Id is " + message.getId() + ", message is " + message.getMessage() + ", parameter good is " + message.isGood();
}
all parameters aren't ignored.
You can use like this,
MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
params.add("id", 1);
params.add("good", true);
params.add("message", 1234);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(params, requestHeaders);
RestTemplate restTemplate = new RestTemplate();
Message message= restTemplate.postForObject("http://localhost:8080/", requestEntity, Message.class);

post request with multiple parameters JSON and String on Jackson/Jersey JAVA

I've created a rest api using Jersey/Jackson and it works well. I want to adjust my POST methods to receive a string token in addition to the POJO they are receiving as JSON. I've adjusted one of my methods like so:
#POST
#Path("/user")
#Consumes(MediaType.APPLICATION_JSON)
public Response createObject(User o, String token) {
System.out.println("token: " + token);
String password = Tools.encryptPassword(o.getPassword());
o.setPassword(password);
String response = DAL.upsert(o);
return Response.status(201).entity(response).build();
}
I want to call that method, but for whatever reason token prints to null no matter what I try. Here is the client code I've written to send the post request:
public String update() {
try {
com.sun.jersey.api.client.Client daclient = com.sun.jersey.api.client.Client
.create();
WebResource webResource = daclient
.resource("http://localhost:8080/PhizzleAPI/rest/post/user");
User c = new User(id, client, permission, reseller, type, username,
password, name, email, active, createddate,
lastmodifieddate, token, tokentimestamp);
JSONObject j = new JSONObject(c);
ObjectMapper mapper = new ObjectMapper();
String request = mapper.writeValueAsString(c) + "&{''token'':,''"
+ "dog" + "''}";
System.out.println("request:" + request);
ClientResponse response = webResource.type("application/json")
.post(ClientResponse.class, request);
if (response.getStatus() != 201) {
throw new RuntimeException("Failed : HTTP error code : "
+ response.getStatus());
}
System.out.println("Output from Server .... \n");
String output = response.getEntity(String.class);
setId(UUID.fromString(output));
System.out.println("output:" + output);
return "" + output;
} catch (UniformInterfaceException e) {
return "failue: " + e.getMessage();
} catch (ClientHandlerException e) {
return "failue: " + e.getMessage();
} catch (Exception e) {
return "failure: " + e.getMessage();
}
}
Any help would be greatly appreciated.
This is not the way JAX-RS works. The body of your POST request will get marshaled to the first argument of your annotated resource method (in this case, into the User argument). You have a couple options to get around this:
Create a wrapper object containing both a User object and token. Send that back and forth between your client and server.
Specify the token as a query parameter on your URL and access it on the server side as a #QueryParam.
Add the token as a header parameter and access it on the server side as a #HeaderParam.
Example - Option 1
class UserTokenContainer implements Serializable {
private User user;
private String token;
// Constructors, getters/setters
}
Example - Option 2
Client:
WebResource webResource = client.
resource("http://localhost:8080/PhizzleAPI/rest/post/user?token=mytoken");
Server:
#POST
Path("/user")
#Consumes(MediaType.APPLICATION_JSON)
public Response createObject(#QueryParam("token") String token, User o) {
System.out.println("token: " + token);
// ...
}
Example - Option 3
Client:
ClientResponse response = webResource
.type("application/json")
.header("Token", token)
.post(ClientResponse.class, request);
Server:
#POST
Path("/user")
#Consumes(MediaType.APPLICATION_JSON)
public Response createObject(#HeaderParam("token") String token, User o) {
System.out.println("token: " + token);
// ...
}
In case you're using Jersey 1.x, best approach is to post multiple objects as #FormParam
At least two advantages:
You don't need to use a wrapper object to post multiple parameters
The parameters are sent within the body rather than in the url (as with #QueryParam and #PathParam)
Check this example:
Client: (pure Java):
public Response testPost(String param1, String param2) {
// Build the request string in this format:
// String request = "param1=1&param2=2";
String request = "param1=" + param1+ "&param2=" + param2;
WebClient client = WebClient.create(...);
return client.path(CONTROLLER_BASE_URI + "/test")
.post(request);
}
Server:
#Path("/test")
#POST
#Produces(MediaType.APPLICATION_JSON)
public void test(#FormParam("param1") String param1, #FormParam("param2") String param2) {
...
}

Categories