This test case is for mocking the health check contract.
TestClass
#Pact(consumer = "Consumer")
public RequestResponsePact getHealthCheck(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "text/plain");
headers.put("callchainid", "a4275861-f60a-44ab-85a6-c0c2c9df5e27");
return builder
.given("get health check")
.uponReceiving("get health data")
.path("/health")
.method("GET")
.headers(headers )
.willRespondWith()
.status(200)
.body("{\"status\":\"UP\",\"components\":{\"db\":{\"status\":\"UP\",\"details\":{\"database\":\"PostgreSQL\",\"validationQuery\":\"isValid()\"}}}}")
.toPact();
}
#Test
#PactTestFor(pactMethod = "getHealthCheck")
void getHealthData(MockServer mockServer) {
WebClient webClient=WebClient.builder().baseUrl(mockServer.getUrl()).build();
final String callChainId="a4275861-f60a-44ab-85a6-c0c2c9df5e27";
ThreadContext.put(CallChainIdService.HEADER_NAME, callChainId);
AsyncClient asyncClient=new AsyncClient(webClient);
Mono<ClientResponse> responseMono=asyncClient.getHealthCheck();
System.out.println(responseMono);
}
Here the webclient end point code which i am trying to hit,
AsyncClient Class
private final WebClient CLIENT;
#Override
public Mono<ClientResponse> getHealthCheck() {
return get(MediaType.TEXT_PLAIN, "/health");
}
private Mono<ClientResponse> get(MediaType contentType, String uri, Object... params) {
return CLIENT
.mutate()
.defaultHeader(CallChainIdService.HEADER_NAME, ThreadContext.get(CallChainIdService.HEADER_NAME))
.build()
.get()
.uri(uri, params)
.accept(contentType)
.exchange();
}
When i run the test , i got PactMismatchesException: The following requests were not received.
au.com.dius.pact.consumer.PactMismatchesException: The following requests were not received:
method: GET
path: /health
query: {}
headers: {callchainid=[a4275861-f60a-44ab-85a6-c0c2c9df5e27], Content-Type=[text/plain]}
matchers: MatchingRules(rules={})
generators: Generators(categories={})
body: MISSING
I am not sure what i am doing wrong here. Appreciate your inputs and help
It looks like you have content-type as the expected header you send in the GET call, but in fact you send only an accept header (both with the media type text/plain).
I think your test should be updated to use accept.
Related
I have a Quarkus application with the following filters definition:
#ApplicationScoped
#Slf4j
public class Filters {
// some #Inject parameters i'm using
#ServerRequestFilter(preMatching = true)
public void requestLoggingFilter(ContainerRequestContext requestContext) {
log.info("Recv: [{}] {}, {}", requestContext.getHeaderString("myHeader"), requestContext.getMethod(), requestContext.getUriInfo().getRequestUri());
}
#ServerResponseFilter
public void responseBasicHeaderFilter(ContainerResponseContext responseContext) {
responseContext.getHeaders().putSingle("myHeader, "myValue");
}
#ServerResponseFilter
public void responseLoggingFilter(ContainerResponseContext responseContext) {
log.info("Sent: [{}] {} {}", responseContext.getHeaderString("myHeader"), , responseContext.getStatusInfo(), responseContext.getEntity());
}
}
And I have two tests:
Test Class config:
#QuarkusTest
public class MyTest {
...
}
Test A:
final Response response = given()
.post(BASE_URL)
.then()
.extract().response();
assertEquals(200, response.getStatusCode(), () -> "Got: " + response.prettyPrint());
assertEquals("myValue", response.getHeader("myHeader"));
final Response response2 = given()
.get(BASE_URL)
.then()
.extract().response();
assertEquals(200, response2.getStatusCode(), () -> "Got: " + response2.prettyPrint());
assertEquals("myValue", response2.getHeader("myHeader"));
Test B:
final Response response = given()
.post(BASE_URL)
.then()
.extract().response();
assertEquals(200, response.getStatusCode(), () -> "Got: " + response.prettyPrint());
assertEquals("myValue", response.getHeader("myHeader"));
If i run Test B on it's own, it passes.
If i run Test A however the last assertion fails (the header value is not there).
The #ServerResponseFilter seem to not be called beyond the first time, however #ServerRequestFilter seem to be fine.
I have tested the api manually and can confirm the same behaviour. Calling the GET request first will also have the same behaviour.
I have verified that the response generated by my Controller (pojo) is generated successfully.
What could be preventing it from being rerun?
Turns out it wasn't related to GET vs POST
my GET method was returning a Multi . I converted this to Uni> and it worked.
From the documentation i found this snippet
Reactive developers may wonder why we can't return a stream of fruits directly. It tends to eb a bad idea with a database....
The keyword being we can't so I imagine this is not supported functionality
I have added the necessary dependencies for spring open feign like mentioned in https://github.com/OpenFeign/feign-form and followed the mentioned configuration for feign-client.
Whenever I sent the post request with content-type as application/x-www-form-urlencoded. The request body is not generated properly.
EmailClient.java
#FeignClient(name = "email", url = "localhost:3000",
configuration = EmailClientConfiguration.class)
public interface EmailClient {
#PostMapping(value = "/email/send")
ResponseDto sendEmail(#RequestBody Map<String, String> requestBody);
}
This is my client configuration Class:
public class EmailClientConfiguration {
#Bean
public RequestInterceptor requestInterceptor(Account<Account> account) {
return template -> {
template.header("Content-Type", "application/x-www-form-urlencoded");
};
}
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
#Bean
public Decoder feignDecoder() {
return new JacksonDecoder();
}
#Bean
public Encoder feignFormEncoder () {
return new SpringFormEncoder(new JacksonEncoder());
}
}
Map<String, String> requestBody = new HashMap<>();
requestBody.put("username", "xyz");
requestBody.put("email", "xyz#gmail.com");
requestBody.put("key", "xxx");
when I called the sendEmail method in interface, the requester headers are set correctly but the request body is sent as
{"{\n \"key\" : \"xxx\",\n \"email\" : \"xyz#gmail.com\",\n \"username\" : \"xyz\"\n}"
Can someone please help on this. Why request body is sent like this. And also the request body is not hidden in the server side though content-type is application/x-www-form-urlencoded.
It works fine after adding consumes.
#FeignClient(name = "email", url = "localhost:3000",
configuration = EmailClientConfiguration.class)
public interface EmailClient {
#PostMapping(value = "/email/send", consumes = "application/x-www-form-urlencoded")
ResponseDto sendEmail(#RequestBody Map<String, String> requestBody);
}
My application under tests has endpoint defined like below:
#ResponseBody
#RequestMapping(value = "maxsize", method = RequestMethod.POST)
public ResponseEntity<Void> changeMaxQuoteSize(#RequestBody DataRequest dataRequest,
#AuthenticationPrincipal UserProfile userProfile) {
orderManager.scheduleUpdateCurrencyConfigRules(dataRequest.getCurrency(),
(c) -> c.setMaxQuoteSize(dataRequest.getMaxSize()))
return ResponseEntity.status(HttpStatus.OK).build();
}
I want to sent message to it using rest-assured but my question is how to map request body to DataRequest object ?
I tried it that way:
class DateRq {
private String curpair;
private Double maxQuoteSize;
public DateRq(String curpair, Double maxQuoteSize) {
this.curpair = curpair;
this.maxQuoteSize = maxQuoteSize;
}
}
#Test
public void test() {
String endpoint = "http://127.0.0.1:8095/api/maxsize";
DateRq request = new DateRq(TICKER_SYMBOL, 5_000_000D);
Response response = RestAssured.given()
.when()
.body(request)
.post(endpoint);
assertEquals(200, response.getStatusCode());
}
but receive such error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com...PMTest$DateRq and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
I tried with some kind of JSON but we didn't receive any response:
#Test
public void test() {
String endpoint = "http://127.0.0.1:8095/api/maxsize";
String request = new JSONObject()
.put("curpair", TICKER_SYMBOL)
.put("maxQuoteSize", 5_000_000D)
.toString();
Response response = RestAssured.given()
.when()
.body(request)
.post(endpoint);
assertEquals(200, response.getStatusCode());
}
Have you tried code like this?
DateRq request = new DateRq(TICKER_SYMBOL, 5_000_000D);
Response response = RestAssured.given()
.body(request)
.when()
.post(endpoint);
When trying to get an access token using OAuth2RequestTemplate the call sends the below header in a request
"Content-Type", "application/x-www-form-urlencoded;charset=UTF-8”
I’d like to drop the charset to not be included there.
I tried to set header value manually through AccessTokenRequest object and a CustomTokenProvider, but it did not work.
Any idea why it is actually including it there and how to get rid of it.
UPDATE : Including the code sample
OPTION 1 :
String oauthServerUri = "..../access_token";
ClientCredentialsResourceDetails ccDetails = new ClientCredentialsResourceDetails();
ccDetails.setClientId("clientId");
ccDetails.setClientSecret("clientSecret");
ccDetails.setGrantType("client_credentials");
ccDetails.setAccessTokenUri(oauthServerUri);
AccessTokenRequest tokenRequest = new DefaultAccessTokenRequest();
Map<String, List<String>> headers = new HashMap<>();
headers.put("Content-Type", Arrays.asList("Some Proper Value"));
tokenRequest.setHeaders(headers);
OAuth2ClientContext context = new DefaultOAuth2ClientContext(tokenRequest);
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(ccDetails, context);
OAuth2AccessToken accessToken = restTemplate.getAccessToken();
OPTION 2 :
As an alternative I have tried the approach described here :
How to set HTTP Header for OAuth2RestTemplate
Which is implementing AccessTokenProvider and setting headers in obtainAccessToken. But this did not help either.
When you make the constructor new OAuth2RestTemplate(ccDetails, context); behind it makes a super() which makes a RestTemplate because it extends from it.
public OAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
super();
if (resource == null) {
throw new IllegalArgumentException("An OAuth2 resource must be supplied.");
}
this.resource = resource;
this.context = context;
setErrorHandler(new OAuth2ErrorHandler(resource));
}
The RestTemplate constructor puts messageConverters by default.
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
......
Maybe a solution could be that once created the object OAuth2RestTemplate you make a restTemplate.setMessageConverters(messageConverters) with the MediaType that interests you, from this method (inside RestTemplate class) deletes the previous ones:
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
// Take getMessageConverters() List as-is when passed in here
if (this.messageConverters != messageConverters) {
this.messageConverters.clear();
this.messageConverters.addAll(messageConverters);
}
}
EDIT:
If you see the image above, you can see when you do:
restTemplate.getAccessToken();
call to
getRequestCallback(resource, form, headers), extractor, form.toSingleValueMap());
protected RequestCallback getRequestCallback(OAuth2ProtectedResourceDetails resource,
MultiValueMap<String, String> form, HttpHeaders headers) {
return new OAuth2AuthTokenCallback(form, headers);
}
and look what his constructor does:
/**
* Request callback implementation that writes the given object to the request stream.
*/
private class OAuth2AuthTokenCallback implements RequestCallback {
private final MultiValueMap<String, String> form;
private final HttpHeaders headers;
private OAuth2AuthTokenCallback(MultiValueMap<String, String> form, HttpHeaders headers) {
this.form = form;
this.headers = headers;
}
public void doWithRequest(ClientHttpRequest request) throws IOException {
request.getHeaders().putAll(this.headers);
request.getHeaders().setAccept(
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED));
FORM_MESSAGE_CONVERTER.write(this.form, MediaType.APPLICATION_FORM_URLENCODED, request);
}
}
Maybe through inheritance and overwriting methods, you can pass on to that builder the headers that interest you.
Also in OAuth2AccessTokenSupport the method retrieveToken has interesting comments:
// Prepare headers and form before going into rest template call in case the URI is affected by the result
authenticationHandler.authenticateTokenRequest(resource, form, headers);
// Opportunity to customize form and headers
tokenRequestEnhancer.enhance(request, resource, form, headers);
I hope I helped you.
You could use a interceptor to wrap your request, see ClientHttpRequestInterceptor#intercept:
intercept
ClientHttpResponse intercept(HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution)
throws IOException
Intercept the given request, and return a response. The given ClientHttpRequestExecution allows the interceptor to pass on the request and response to the next entity in the chain.
A typical implementation of this method would follow the following pattern:
Examine the request and body
Optionally wrap the request to filter HTTP attributes.
Optionally modify the body of the request.
Either
execute the request using ClientHttpRequestExecution.execute(org.springframework.http.HttpRequest, byte[]),
or
do not execute the request to block the execution altogether.
Optionally wrap the response to filter HTTP attributes.
Your modified code:
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(ccDetails, context);
restTemplate.setInterceptors(Arrays.asList(new new RestTemplateHeaderModifierInterceptor()));
OAuth2AccessToken accessToken = restTemplate.getAccessToken();
with
public class RestTemplateHeaderModifierInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpRequest requestWrapper = new CustomHttpRequestWrapper(request);
return execution.execute(requestWrapper, body);
}
}
public class CustomHttpRequestWrapper extends HttpRequestWrapper {
public CustomHttpRequestWrapper(HttpRequest request) {
super(request)
}
#Override
public HttpHeaders getHeaders() {
// return all headers, but change the charset
}
}
I've been debugging this for three hours, I still cannot explain why my custom headers (registered via a client request filter) are not sent.
The client is configured as such (full source here):
private WebTarget webTarget(String host, String appId, String appKey) {
return newClient(clientConfiguration(appId, appKey))
.target(host + "/rest");
}
private Configuration clientConfiguration(String appId, String appKey) {
ClientConfig config = new ClientConfig();
config.register(requestFilter(appId, appKey));
return config;
}
private ClientRequestFilter requestFilter(String appId, String appKey) {
return new VidalRequestFilter(apiCredentials(appId, appKey));
}
The filter is as follows:
public class VidalRequestFilter implements ClientRequestFilter {
private final ApiCredentials credentials;
public VidalRequestFilter(ApiCredentials credentials) {
this.credentials = credentials;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
headers.add(ACCEPT, APPLICATION_ATOM_XML_TYPE);
headers.add("app_id", credentials.getApplicationId());
headers.add("app_key", credentials.getApplicationKey());
}
}
And the call is like:
String response = webTarget
.path("api/packages")
.request()
.get()
.readEntity(String.class);
All I get is 403 forbidden, because the specific endpoint I am calling is protected (the auth is performed with the custom headers defined above).
The weirdest thing is that, while I'm debugging, I see that sun.net.www.MessageHeader is properly invoked during the request write (i.e. the instance is valued as such: sun.net.www.MessageHeader#14f9390f7 pairs: {GET /rest/api/packages HTTP/1.1: null}{Accept: application/atom+xml}{app_id: XXX}{app_key: YYY}{User-Agent: Jersey/2.22.1 (HttpUrlConnection 1.8.0_45)}{Host: ZZZ}{Connection: keep-alive}.
However, I have the confirmation that neither our API server, nor its reverse proxy received GET requests with the required auth headers (a first HEAD request seems to be OK, though).
I know for sure the credentials are good 'cause the equivalent curl command just works!
I tried the straightforward approach to set headers directly when defining the call without any success.
What am I missing?