Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wiremock simulate a proxy server running

I want to add a test for the following e2e scenario:

My app is making a web request to an external service through an internal proxy server, the proxy server manipulates the request body, forwards the request to the desination host and returns the response returned.

Say for example I do a post request to external.service/an/endpoint (through my-proxy-server) the body

{
"card_number": "<proxy server pls fill the cc details>"
}

The proxy server modifies the request to fill the cc details and forwards it to external.service/an/endpoint with body

{
"card_number": "372735466563005"
}

The external.service returns status OK. proxy server returns the response without modifying.

How do I test this workflow with wiremock? I can do WireMock.stubFor() for external.service, But I don't know how to make wiremock proxy work with the proxy setting of my Webclient. See the test, actually, Rest Template test, restTemplateWithWireMockAsProxyServer works as expected, routing my requests through the proxy, but the webClientWithWireMockAsProxyServer errors out with my RCA:

20:06:59.165 [qtp105751207-24] DEBUG wiremock.org.eclipse.jetty.server.HttpChannel - REQUEST for //localhost:58978localhost:58978 on HttpChannelOverHttp@4a71ab50{r=1,c=false,c=false/false,a=IDLE,uri=//localhost:58978localhost:58978,age=0}
CONNECT //localhost:58978localhost:58978 HTTP/1.1
Host: localhost:58978

These Calls over wiremock proxy are not possible as mentioned here. But all my urls are like http://localhost:<port>, meaning I am not making any https call.

package com.spotnana.obt.supplier.services;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.http.RequestMethod;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.nio.charset.StandardCharsets;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.apache.http.conn.HttpHostConnectException;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.client.reactive.ReactorResourceFactory;
import org.springframework.util.SocketUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.ProxyProvider;

@Slf4j
public class SimpleWiremockProxyServerTest {

  private final String HOST = "localhost";
  private final String MOCK_ENDPOINT = "/my/endpoint";
  private WireMockServer targetServer;
  private WireMockServer proxyServer;
  private WireMock targetWireMock;
  private WireMock proxyWireMock;
  private String targetBaseUrl;

  @Before
  public void setup() {
    final int targetPort = SocketUtils.findAvailableTcpPort();
    this.targetServer = new WireMockServer(WireMockConfiguration.wireMockConfig().port(targetPort));
    this.targetServer.start();
    this.targetWireMock = new WireMock(targetPort);
    this.targetWireMock.resetMappings();
    this.targetBaseUrl = "http://" + HOST + ":" + targetPort;

    final int proxyPort = SocketUtils.findAvailableTcpPort();
    this.proxyServer =
        new WireMockServer(
            WireMockConfiguration.wireMockConfig().port(proxyPort).enableBrowserProxying(true));
    this.proxyServer.start();
    this.proxyWireMock = new WireMock(proxyPort);
    this.proxyWireMock.resetMappings();
  }

  @After
  public void tearDown() throws HttpHostConnectException {
    this.targetWireMock.shutdown();
    this.targetServer.stop();

    try {
      this.proxyWireMock.shutdown();
      this.proxyServer.stop();
    } catch (final Exception ex) {
      log.warn("Proxy server is shutdown already");
    }
  }

  @Test
  public void restTemplateWithWireMockAsProxyServer() {
    SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

    Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(HOST, this.proxyServer.port()));
    requestFactory.setProxy(proxy);

    final var reqPatternBuilder =
        RequestPatternBuilder.newRequestPattern(
            RequestMethod.GET, WireMock.urlEqualTo(MOCK_ENDPOINT));
    final var mappingBuilder =
        WireMock.get(WireMock.urlEqualTo(reqPatternBuilder.build().getUrl()));

    reqPatternBuilder
        .withHeader(HttpHeaders.ACCEPT, WireMock.containing(MediaType.APPLICATION_JSON_VALUE))
        .withHeader(
            HttpHeaders.ACCEPT_CHARSET,
            WireMock.containing(StandardCharsets.UTF_8.name().toUpperCase()));
    mappingBuilder.willReturn(
        WireMock.aResponse()
            .withStatus(HttpStatus.OK.value())
            .withBody("{ \"success\": true }")
            .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));
    this.targetWireMock.register(mappingBuilder);

    ResponseEntity<String> responseEntity =
        new RestTemplate(requestFactory)
            .getForEntity(this.targetBaseUrl + MOCK_ENDPOINT, String.class);
    Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
    System.out.println("responseEntity: " + responseEntity.getBody());
  }

  @Test
  public void webClientWithWireMockAsProxyServer() {
    var client = HttpClient.create()
        .tcpConfiguration(
            tcpClient ->
                tcpClient.proxy(
                    proxy -> {
                      proxy
                          .type(ProxyProvider.Proxy.HTTP)
                          .host(HOST)
                          .port(this.proxyServer.port());
                    }));
    var webClient = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(client))
        .build();

    final var reqPatternBuilder =
        RequestPatternBuilder.newRequestPattern(
            RequestMethod.GET, WireMock.urlEqualTo(MOCK_ENDPOINT));
    final var mappingBuilder =
        WireMock.get(WireMock.urlEqualTo(reqPatternBuilder.build().getUrl()));

    reqPatternBuilder
        .withHeader(HttpHeaders.ACCEPT, WireMock.containing(MediaType.APPLICATION_JSON_VALUE))
        .withHeader(
            HttpHeaders.ACCEPT_CHARSET,
            WireMock.containing(StandardCharsets.UTF_8.name().toUpperCase()));
    mappingBuilder.willReturn(
        WireMock.aResponse()
            .withStatus(HttpStatus.OK.value())
            .withBody("{ \"success\": true }")
            .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));
    this.targetWireMock.register(mappingBuilder);

    var response =
        webClient.get().uri(this.targetBaseUrl + MOCK_ENDPOINT).exchange().block().bodyToMono(String.class);
    response.subscribe(x -> System.out.println("x:" + x));
  }

}

I complains with error java.net.UnknownHostException: <proxy server>: nodename nor servname provided, or not known. Is there a way to mock the wiremock proxy server, rather than running an actual server for this. I also want to put validations in the proxy server for request-responses.

like image 433
v78 Avatar asked Nov 08 '20 15:11

v78


People also ask

What is a proxy stub?

A proxy is a placeholder, or representative for something real. A stub is usually some code you write just to help you out with unit testing.


1 Answers

Wiremock doesn't support HTTP CONNECT method. You can try Hoverfly as an alternative to Wiremock. There is a github issue if you're interested in details.

like image 129
Max Avatar answered Sep 28 '22 03:09

Max