Spring cloud contract best practice to handle unhappy path - spring

We used to have wiremock for integration tests with both happy and unhappy paths. Now we are trying to move to Spring cloud contract based integration tests. Though, I could not find any document related to contracts for unhappy paths (status code over 400). And I did some POC with status code 4xx/5xx in response but it didnot work.
Anyone knows the best practice to handle unhappy paths in consumer side? or is it not supported at all for unhappy paths with status code over 400 with spring cloud contract?

Here is an example:
Producer side
Contract.make {
description 'get 404 when entity was not found'
request {
method GET()
url '/entities/0'
}
response {
status NOT_FOUND()
}
}
Client side
#RunWith(SpringRunner.class)
#SpringBootTest(classes = SomeApplication.class)
#AutoConfigureStubRunner(ids = "io.app:entity:+:stubs:8080")
#AutoConfigureTestDatabase
public class EntityClientTest {
#Rule
public ExpectedException exception = ExpectedException.none();
#Autowired
private EntityClient entityClient; // This is a FeignClient
#Test
public void shouldThrowNotFoundWithInvalidId() {
exception.expect(FeignException.class);
exception.expectMessage("404");
entityClient.getById(0);
}
}
As you can see, the getById thrown a 404 because the contract says so.

Related

What's the best usage for mockOIdcLogin() for WebTestClient?

I'm currently working on a senior project and we decided to use Spring Webflux for our backend and Google as our OAuth2.0 provider. I am currently trying to run some integration tests using Spock and Groovy on some endpoints that are secured behind OAuth2.0 authentication. The endpoint does not use the Authentication principal for anything, is just not supposed to be accessed by someone who isn't authenticated. However, reading the Spring documentation and I came across the method for a webTestClient to use a mock open id connect login in which I might not need to do mock the entire OAuth2 process, however, this is giving me a HTTP 302 status
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = "spring.main.web-application-type=reactive")
class UserControllerITSpec extends Specification {
#Autowired
ReactiveWebApplicationContext context
#Autowired
ApplicationContext applicationContext
#Autowired
WebTestClient client
#Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig()
.port(8077))
def setup() {
client = WebTestClient.bindToApplicationContext(context)
.configureClient()
.build()
}
def "getAllUsers is successful"() {
given: "a request"
def request = client.mutateWith(mockOidcLogin()).get().uri("/api/v2/users/getAllUsers")
stubFor(post("/graphql/")
.withHeader("Authorization", equalTo("F9v4MUqdQuWAh3Wqxe11mteqPfPedUqp78VaQNJt8DSt"))
.withHeader("content-type", equalTo("application/json"))
.withHeader("accept", equalTo("application/json"))
.withRequestBody(equalTo("""{
"query": "query { list_UserItems { _UserItems { _id email displayName appointments } } }",
"variables": null,
"operationName": null
}"""))
.willReturn(aResponse()
.withStatus(200)
.withBodyFile("vendiaResponses/getAllUsersResponse.json")))
stubFor(get("/oauth2/authorization/wiremock")
.willReturn(status(200)))
when: "the request is sent"
def response = request.exchange()
then: "an OK status is returned"
response.expectStatus()
.isOk()
}
}
Is my understanding of the mutateWith(mockOidcLogin()) method incorrect?
I'd consider using webEnvironment = SpringBootTest.WebEnvironment.MOCK
Are you sure about the Authentication implementation you have at runtime? (you can have Authentication auth auto-magically injected as #Controller method parameter and inspect it with your favorite debugger or log its class)
Isn't there anything of help in those samples: https://github.com/ch4mpy/spring-addons/tree/master/samples (look at those containing webflux in their name and focus on test classes ending with ItegrationTest in their name like this one)
If their isn't a test annotation for your spring Authentication type in the repo yet, open an issue. I might consider implement it.

Error handling on quarkus mutiny rest client

On my quarkus rest project i have a restclient that uses mutiny:
#Path("/")
#RegisterRestClient(configKey = "my-api")
#RegisterClientHeaders
#RegisterProvider(MyExceptionMapper.class)
public interface MyClient {
#POST
#Path("path")
Uni<MyBean> get(String body);
}
I wanna handle propery non 2XX httpError so i have made my ExceptionMaper
public class MyExceptionMapper implements ResponseExceptionMapper<MyException> {
#Override
public MyException toThrowable(Response response) {
//TODO
return new MyException();
}
}
a bad call on the client shows that MyExceptionMapper handle the response but the exception raises and does not became a failure on my Uni Client response object
Uni<MyBean> bean = myClient.get("") // i do not have a failure in case of 4XX http
.onFailure().invoke(fail -> System.out.println("how can i get here?"));
Am i using mutiny on a rest client in the wrong way?
Thanks
UPDATE
ok i forgot to add the dependency quarkus-rest-client-mutiny, adding this i notice 2 things,
i still pass through Myexceptionmapper
i also produce a Uni.failure, but the exception into the failure is not the custom exception i created into MyExceptionmapper but a RestEasyWebApplicationException
Failure : org.jboss.resteasy.client.exception.ResteasyWebApplicationException: Unknown error, status code 400
at org.jboss.resteasy.client.exception.WebApplicationExceptionWrapper.wrap(WebApplicationExceptionWrapper.java:107)
at org.jboss.resteasy.microprofile.client.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:21)
Does the ExceptionMapper becomes useless in this context?
I think this is a bug in quarkus-rest-client-mutiny. I created an Github issue based on your findings.
It will work as you expect if you switch to quarkus-rest-client-reactive

Resolving POST /** request URL to full request URL using micrometer

With the micro-service architecture I have written a generic POST request handler which is consumed by all the micro-services. The post mapping in spring look like this:
#RestController
#RequestMapping(value = "/v1/", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE})
public class V1Controller {
#PostMapping(path = "/**")
public #ResponseBody Json post () {}
}
Now while I am consuming the metrics for this endpoint using micrometer I am only getting /v1/ as the endpoint in the metrics while I am sending the full URL like /v1/demo/foo from the calling service. I tried lot of the combination but it is not working. I have also added the WebMvcTagsProvider where I am listing to request and resolving the POST api calls.
#Bean
#SuppressWarnings("unchecked")
public WebMvcTagsProvider webMvcTagsProvider(ObjectMapper objectMapper) {
return new DefaultWebMvcTagsProvider() {
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
if ("POST".equals(request.getMethod())) {
Tag uriTag = Tag.of("uri", String.valueOf(request.getRequestURI()));
return Tags.of(WebMvcTags.method(request), uriTag, WebMvcTags.exception(exception), WebMvcTags.status(response));
}
return Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response), WebMvcTags.exception(exception), WebMvcTags.status(response));
}
};
}
Still it is resolving to /v1/ URL in the metrics. I tried googling alot but didn't find any solution. Thanks in advance.
The build in Spring Boot RequestMapping based metrics match on the annotations and add those as tags.
This is to avoid a tag explosion. Imagine a #RequestMapping for a path like user/{userId}, you would want to group all those calls together (user/1, user/2, user/3).
You'll want to create your own Timer in your post method that set that url tags, etc there.
If you decide to reuse the same metric name as the built in Spring Boot metric, you'll want to disable that one as well, so you don't double count those requests.

Cannot properly test ErrorController Spring Boot

due to this tutorial - https://www.baeldung.com/spring-boot-custom-error-page I wanted to customize my error page ie. when someone go to www.myweb.com/blablablalb3 I want to return page with text "wrong url request".
All works fine:
#Controller
public class ApiServerErrorController implements ErrorController {
#Override
public String getErrorPath() {
return "error";
}
#RequestMapping("/error")
public String handleError() {
return "forward:/error-page.html";
}
}
But I dont know how to test it:
#Test
public void makeRandomRequest__shouldReturnErrorPage() throws Exception {
this.mockMvc.perform(get(RANDOM_URL))
.andDo(print());
}
print() returns:
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {X-Application-Context=[application:integration:-1]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
So I cant created something like this:
.andExpect(forwardedUrl("error-page"));
because it fails, but on manual tests error-page is returned.
Testing of a custom ErrorController with MockMvc is unfortunately not supported.
For a detailed explanation, see the official recommendation from the Spring Boot team (source).
To be sure that any error handling is working fully, it's necessary to
involve the servlet container in that testing as it's responsible for
error page registration etc. Even if MockMvc itself or a Boot
enhancement to MockMvc allowed forwarding to an error page, you'd be
testing the testing infrastructure not the real-world scenario that
you're actually interested in.
Our recommendation for tests that want to be sure that error handling
is working correctly, is to use an embedded container and test with
WebTestClient, RestAssured, or TestRestTemplate.
My suggestion is to use #ControllerAdvice
In this way you can work around the problem and you can continue to use MockMvc with the big advantage that you are not required to have a running server.
Of course to test explicitly the error page management you need a running server. My suggestion is mainly for those who implemented ErrorController but still want to use MockMvc for unit testing.
#ControllerAdvice
public class MyControllerAdvice {
#ExceptionHandler(FileSizeLimitExceededException.class)
public ResponseEntity<Throwable> handleFileException(HttpServletRequest request, FileSizeLimitExceededException ex) {
return new ResponseEntity<>(ex, HttpStatus.PAYLOAD_TOO_LARGE);
}
#ExceptionHandler(Throwable.class)
public ResponseEntity<Throwable> handleUnexpected(HttpServletRequest request, Throwable throwable) {
return new ResponseEntity<>(throwable, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

integrate multiple service response in spring cloud gateway

When I get request form path for example /bar is it possible in spring cloud gateway to call multiple microservices and integrate their result (for example JSON) and send as response of /bar ?
How can i do it?
thanks
You can use ProxyExchange to help you composition multiple responses.
An example given by Spring Cloud:
#RestController
#SpringBootApplication
public class GatewaySampleApplication {
#Value("${remote.home}")
private URI home;
#GetMapping("/test")
public ResponseEntity<?> proxy(ProxyExchange<byte[]> proxy) throws Exception {
return proxy.uri(home.toString() + "/image/png").get();
}
}
In this case it is only used to return the ResponseEntity, but you can use it however you like. In your case you can combine multiple ResponseEntities.

Resources